diff --git a/.gitignore b/.gitignore index 51ceac19..bbdc99d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,19 @@ -out/* -out +# ============================== +# IDE .idea/* .idea .vs/* .vs + +# ============================== +# scripts + +# CPM.cmake & CPM.cmake.tmp +# see scripts/external_installer.cmake +CPM.cmake* + +# ============================== +# OUTPUT +out/* +out *.pdb diff --git a/3rd-party/stb/stb.cmake b/3rd-party/stb/stb.cmake deleted file mode 100644 index c4e9ac85..00000000 --- a/3rd-party/stb/stb.cmake +++ /dev/null @@ -1,9 +0,0 @@ -function(link_3rd_library_stb project_name) - target_include_directories( - ${project_name} - PRIVATE - ${${PROJECT_NAME_PREFIX}3RD_PARTY_PATH}/stb/include - ) - - # header-only -endfunction(link_3rd_library_stb project_name) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f8b717a..873788c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,20 +1,5 @@ cmake_minimum_required(VERSION 3.25) -if (CMAKE_VERSION VERSION_LESS "3.26") - # 3.25 - set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "3c375311-a3c9-4396-a187-3227ef642046") -elseif (CMAKE_VERSION VERSION_LESS "3.27") - # 3.26 - set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "2182bf5c-ef0d-489a-91da-49dbc3090d2a") -elseif (CMAKE_VERSION VERSION_LESS "3.28") - # 3.27 - set(CMAKE_EXPERIMENTAL_CXX_MODULE_CMAKE_API "aa1f7df0-828a-4fcd-9afc-2dc80491aca7") -elseif (NOT CMAKE_VERSION VERSION_GREATER "3.28") - message(FATAL_ERROR "See `https://github.com/Kitware/CMake/blob/v${CMAKE_VERSION}/Help/dev/experimental.rst`.") -endif (CMAKE_VERSION VERSION_LESS "3.26") -# turn on the dynamic depends for ninja -set(CMAKE_EXPERIMENTAL_CXX_MODULE_DYNDEP ON) - set(PROJECT_NAME_PREFIX "GAL_PROMETHEUS_") set( @@ -24,7 +9,7 @@ set( set( ${PROJECT_NAME_PREFIX}MINOR_VERSION - 6 + 8 ) set( @@ -33,35 +18,13 @@ set( ) set(${PROJECT_NAME_PREFIX}VERSION ${${PROJECT_NAME_PREFIX}MAJOR_VERSION}.${${PROJECT_NAME_PREFIX}MINOR_VERSION}.${${PROJECT_NAME_PREFIX}PATCH_VERSION}) +set(${PROJECT_NAME_PREFIX}VERSION_NAMESPACE_NAME v_${${PROJECT_NAME_PREFIX}MAJOR_VERSION}_${${PROJECT_NAME_PREFIX}MINOR_VERSION}_${${PROJECT_NAME_PREFIX}PATCH_VERSION}) -# Determine if our project is built as a subproject (using add_subdirectory) -# or if it is the master project. set(${PROJECT_NAME_PREFIX}MASTER_PROJECT OFF) if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) set(${PROJECT_NAME_PREFIX}MASTER_PROJECT ON) -else () endif (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) -# fetch remote cmake utils -include(${CMAKE_CURRENT_SOURCE_DIR}/cmake_utils/fetch_utils.cmake) - -include(${CMAKE_CURRENT_SOURCE_DIR}/cmake_utils/doc_var.cmake) - -# Set the default CMAKE_BUILD_TYPE to RelWithDebInfo. -# This should be done before the project command since the latter can set -# CMAKE_BUILD_TYPE itself. -if (${PROJECT_NAME_PREFIX}MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE) - cmake_language( - CALL - ${PROJECT_NAME_PREFIX}doc_var - CMAKE_BUILD_TYPE - RelWithDebInfo - STRING - "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or " - "CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel." - ) -endif (${PROJECT_NAME_PREFIX}MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE) - project( prometheus VERSION ${${PROJECT_NAME_PREFIX}VERSION} @@ -70,58 +33,44 @@ project( LANGUAGES CXX ) -message(STATUS "CMAKE VERSION: ${CMAKE_VERSION}. Compiler ID: ${CMAKE_CXX_COMPILER_ID}. Platform: ${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}.") -message(STATUS "${PROJECT_NAME} VERSION: ${${PROJECT_NAME_PREFIX}VERSION}") -message(STATUS "${PROJECT_NAME} BUILD TYPE: ${CMAKE_BUILD_TYPE}") - -include(GNUInstallDirs) -include(CheckCXXCompilerFlag) - -include(${PROJECT_SOURCE_DIR}/cmake_utils/CPM.cmake) -# Let CPMAddPackage first check if the required package exists locally. -# Compared to CPMFindPackage, CPMAddPackage will call cpm_export_variables after finding the local package. -set(CPM_USE_LOCAL_PACKAGES ON) -include(${PROJECT_SOURCE_DIR}/cmake_utils/cpm_install.cmake) -include(${PROJECT_SOURCE_DIR}/cmake_utils/nuget_install.cmake) -set(${PROJECT_NAME_PREFIX}3RD_PARTY_PATH ${PROJECT_SOURCE_DIR}/3rd-party) - -include(${${PROJECT_NAME_PREFIX}3RD_PARTY_PATH}/freetype/freetype.cmake) -include(${${PROJECT_NAME_PREFIX}3RD_PARTY_PATH}/stb/stb.cmake) -include(${${PROJECT_NAME_PREFIX}3RD_PARTY_PATH}/glfw/glfw.cmake) - -if (${CMAKE_SYSTEM_NAME} MATCHES "Windows") - set(${PROJECT_NAME_PREFIX}PLATFORM_WINDOWS ON) - set(${PROJECT_NAME_PREFIX}PLATFORM ${PROJECT_NAME_PREFIX}PLATFORM_WINDOWS) -elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") - set(${PROJECT_NAME_PREFIX}PLATFORM_LINUX ON) - set(${PROJECT_NAME_PREFIX}PLATFORM ${PROJECT_NAME_PREFIX}PLATFORM_LINUX) -elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - set(${PROJECT_NAME_PREFIX}PLATFORM_MACOS ON) - set(${PROJECT_NAME_PREFIX}PLATFORM ${PROJECT_NAME_PREFIX}PLATFORM_MACOS) -else () - message(FATAL_ERROR "Unsupported Platform: ${CMAKE_SYSTEM_NAME}") -endif (${CMAKE_SYSTEM_NAME} MATCHES "Windows") - -# check cpu features -include(${CMAKE_CURRENT_SOURCE_DIR}/cmake_utils/check_cpu_features.cmake) +if (${PROJECT_NAME_PREFIX}MASTER_PROJECT) + if (NOT DEFINED CMAKE_BUILD_TYPE) + set( + CMAKE_BUILD_TYPE + RelWithDebInfo + CACHE + STRING + "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel." + ) + endif (NOT DEFINED CMAKE_BUILD_TYPE) + + if (NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET) + set( + CMAKE_CXX_VISIBILITY_PRESET + hidden + CACHE + STRING + "Preset for the export of private symbols" + ) + set(CMAKE_CXX_VISIBILITY_PRESET hidden) + set(CMAKE_VISIBILITY_INLINES_HIDDEN TRUE) + endif (NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET) +endif (${PROJECT_NAME_PREFIX}MASTER_PROJECT) -cmake_language( - CALL - ${PROJECT_NAME_PREFIX}doc_var +set( ${PROJECT_NAME_PREFIX}INSTALL_HEADERS ${CMAKE_INSTALL_INCLUDEDIR} + CACHE STRING - "Installation directory for include files, a relative path that " - "will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute path." + "Installation directory for include files, a relative path that will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute path." ) option(${PROJECT_NAME_PREFIX}PEDANTIC "Enable extra warnings and expensive tests." ON) option(${PROJECT_NAME_PREFIX}WERROR "Halt the compilation with an error on compiler warnings." OFF) -option(${PROJECT_NAME_PREFIX}MODULE "Enable module." OFF) -option(${PROJECT_NAME_PREFIX}DOC "Generate the doc target." ${${PROJECT_NAME_PREFIX}MASTER_PROJECT}) # Do we have the documentation? :) +option(${PROJECT_NAME_PREFIX}ASAN "Enable AddressSanitizer." OFF) +option(${PROJECT_NAME_PREFIX}DOC "Generate the doc target." ${${PROJECT_NAME_PREFIX}MASTER_PROJECT}) option(${PROJECT_NAME_PREFIX}INSTALL "Generate the install target." ${${PROJECT_NAME_PREFIX}MASTER_PROJECT}) option(${PROJECT_NAME_PREFIX}TEST "Generate the test target." ${${PROJECT_NAME_PREFIX}MASTER_PROJECT}) -option(${PROJECT_NAME_PREFIX}SYSTEM_HEADERS "Expose headers with marking them as system.(This allows other libraries that use this library to ignore the warnings generated by this library.)" OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) #set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -133,557 +82,16 @@ if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) endif (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) -# Macros about the compiler. -set(${PROJECT_NAME_PREFIX}COMPILER_ID ${CMAKE_CXX_COMPILER_ID}) -set(${PROJECT_NAME_PREFIX}COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION}) -set(${PROJECT_NAME_PREFIX}COMPILER_NAME ${${PROJECT_NAME_PREFIX}COMPILER_ID}.${${PROJECT_NAME_PREFIX}COMPILER_VERSION}) - -if (${PROJECT_NAME_PREFIX}MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET) - cmake_language( - CALL - ${PROJECT_NAME_PREFIX}doc_var - CMAKE_CXX_VISIBILITY_PRESET - hidden - STRING - "Preset for the export of private symbols" - ) - set_property( - CACHE CMAKE_CXX_VISIBILITY_PRESET - PROPERTY STRINGS - hidden default - ) -endif (${PROJECT_NAME_PREFIX}MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET) - -if (${PROJECT_NAME_PREFIX}PLATFORM_WINDOWS) - ######################### - # MSVC / CLANG-CL - ######################## - set(${PROJECT_NAME_PREFIX}COMPILE_FLAGS "/D_CRT_SECURE_NO_WARNINGS") - if (${PROJECT_NAME_PREFIX}PEDANTIC) - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "/W4") - else () - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "/W3") - endif (${PROJECT_NAME_PREFIX}PEDANTIC) - if (${PROJECT_NAME_PREFIX}WERROR) - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "/WX") - endif () - - if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - if (${${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED}) - # chars/icelake_xxx - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-mavx512f") - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-mavx512bw") - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-mavx512vbmi") - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-mavx512vbmi2") - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-mavx512vpopcntdq") - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-mbmi2") - - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-mavx512vl") - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-mavx512dq") - endif (${${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED}) - endif (NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") -elseif (${PROJECT_NAME_PREFIX}PLATFORM_LINUX) - ######################### - # GCC / CLANG - ######################## - set(${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-Wall") - if (${PROJECT_NAME_PREFIX}PEDANTIC) - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-Wextra" "-Wpedantic") - endif (${PROJECT_NAME_PREFIX}PEDANTIC) - if (${PROJECT_NAME_PREFIX}WERROR) - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-Werror") - endif () - - # - #list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "--enable-libstdcxx-backtrace") - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-lstdc++_libbacktrace") - - if (${${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED}) - # chars/icelake_xxx - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-mavx512f") - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-mavx512bw") - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-mavx512vbmi") - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-mavx512vbmi2") - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-mavx512vpopcntdq") - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-mbmi2") - endif (${${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED}) -elseif (${PROJECT_NAME_PREFIX}PLATFORM_MACOS) - ######################### - # APPLE CLANG - ######################## - set(${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-Wall") - if (${PROJECT_NAME_PREFIX}PEDANTIC) - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-Wextra" "-Wpedantic") - endif (${PROJECT_NAME_PREFIX}PEDANTIC) - if (${PROJECT_NAME_PREFIX}WERROR) - list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-Werror") - endif () -else () - message(FATAL_ERROR "Unsupported compilers: ${CMAKE_CXX_COMPILER}") -endif (${PROJECT_NAME_PREFIX}PLATFORM_WINDOWS) - -if (${PROJECT_NAME_PREFIX}SYSTEM_HEADERS) - set(${PROJECT_NAME_PREFIX}HEADER_ATTRIBUTES SYSTEM) -else () - set(${PROJECT_NAME_PREFIX}HEADER_ATTRIBUTES) -endif (${PROJECT_NAME_PREFIX}SYSTEM_HEADERS) - -# LIBRARY -add_library( - ${PROJECT_NAME} - ${${PROJECT_NAME_PREFIX}MODULE_KEYWORD_INTERFACE} -) - -# LIBRARY ALIAS -add_library( - gal::${PROJECT_NAME} - ALIAS - ${PROJECT_NAME} -) - -if (${PROJECT_NAME_PREFIX}MODULE) - set(${PROJECT_NAME_PREFIX}TEMP_CXX_MODULE_PATH ${CMAKE_BINARY_DIR}/temp_module) - if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - if (NOT DEFINED ENV{VCToolsInstallDir}) - message(FATAL_ERROR "Unable to find path to VC Tools in environment variables!") - endif (NOT DEFINED ENV{VCToolsInstallDir}) - - if (NOT EXISTS $ENV{VCToolsInstallDir}) - message(FATAL_ERROR "Invalid VC Tools installation path!") - else () - message(STATUS "Found VC Tools in: [$ENV{VCToolsInstallDir}]") - endif (NOT EXISTS $ENV{VCToolsInstallDir}) - - if (NOT EXISTS $ENV{VCToolsInstallDir}modules/std.ixx) - message(FATAL_ERROR "Cannot find std.ixx! Please check MSVC version(${CMAKE_CXX_COMPILER_VERSION})!") - else () - message(STATUS "Found module std.ixx in: [$ENV{VCToolsInstallDir}modules\\std.ixx]") - endif (NOT EXISTS $ENV{VCToolsInstallDir}modules/std.ixx) - - configure_file( - $ENV{VCToolsInstallDir}modules/std.ixx - ${${PROJECT_NAME_PREFIX}TEMP_CXX_MODULE_PATH}/std.ixx - COPYONLY - ) - - set_source_files_properties( - ${${PROJECT_NAME_PREFIX}TEMP_CXX_MODULE_PATH}/std.ixx - PROPERTIES - COMPILE_OPTIONS "/Wv:18" - ) - - target_sources( - ${PROJECT_NAME} - # fixme: Possible conflicts with other libraries? - PUBLIC - FILE_SET std_module - TYPE CXX_MODULES - BASE_DIRS "${PROJECT_SOURCE_DIR}" - FILES - - $<$:${${PROJECT_NAME_PREFIX}TEMP_CXX_MODULE_PATH}/std.ixx> - ) - elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - # https://gitlab.kitware.com/cmake/cmake/-/blob/master/.gitlab/ci/cxx_modules_rules_clang.cmake - # Default to C++ extensions being off. Clang's modules support have trouble - # with extensions right now. - set(CMAKE_CXX_EXTENSIONS OFF) - - # fixme: https://discourse.cmake.org/t/c-20-modules-update/7330/24 - # fixme: - # PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. - # Stack dump: - # 0. Program arguments: "C:/Program Files/Microsoft Visual Studio/2022/Preview/VC/Tools/Llvm/x64/bin/clang-scan-deps.exe" -format=p1689 -- C:\\PROGRA~1\\MIB055~1\\2022\\Preview\\VC\\Tools\\Llvm\\x64\\bin\\clang-cl.exe -DGAL_PROMETHEUS_COMPILER_CLANG_CL -DGAL_PROMETHEUS_COMPILER_ID=\"Clang\" -DGAL_PROMETHEUS_COMPILER_NAME=\"Clang.16.0.5\" -DGAL_PROMETHEUS_COMPILER_VERSION=\"16.0.5\" -DGAL_PROMETHEUS_MAJOR_VERSION=0 -DGAL_PROMETHEUS_MINOR_VERSION=0 -DGAL_PROMETHEUS_PATCH_VERSION=6 -DGAL_PROMETHEUS_PLATFORM_WINDOWS -DGAL_PROMETHEUS_VERSION=\"0.0.6\" -IC:\workspace\life4gal\prometheus\src -x c++ C:\workspace\life4gal\prometheus\src\infrastructure\type_traits.ixx -c -o CMakeFiles\prometheus.dir\src\infrastructure\type_traits.ixx.obj -MT CMakeFiles\prometheus.dir\src\infrastructure\type_traits.ixx.obj.ddi -MD -MF CMakeFiles\prometheus.dir\src\infrastructure\type_traits.ixx.obj.ddi.d > CMakeFiles\prometheus.dir\src\infrastructure\type_traits.ixx.obj.ddi" - # Exception Code: 0xC0000005 - # #0 0x00007ff7b4803c75 C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\Llvm\x64\bin\clang-scan-deps.exe 0x3c75 C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\Llvm\x64\bin\clang-scan-deps.exe 0x3efc9d0 - # #1 0x00007ff7b4803c75 (C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\Llvm\x64\bin\clang-scan-deps.exe+0x3c75) - # #2 0x00007ff7b86fc9d0 (C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\Llvm\x64\bin\clang-scan-deps.exe+0x3efc9d0) - # 0x00007FF7B4803C75, C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\Llvm\x64\bin\clang-scan-deps.exe(0x00007FF7B4800000) + 0x3C75 byte(s) - # 0x00007FF7B86FC9D0, C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\Llvm\x64\bin\clang-scan-deps.exe(0x00007FF7B4800000) + 0x3EFC9D0 byte(s) - # 0x00007FFA728526AD, C:\WINDOWS\System32\KERNEL32.DLL(0x00007FFA72840000) + 0x126AD byte(s), BaseThreadInitThunk() + 0x1D byte(s) - # 0x00007FFA72C2AA68, C:\WINDOWS\SYSTEM32\ntdll.dll(0x00007FFA72BD0000) + 0x5AA68 byte(s), RtlUserThreadStart() + 0x28 byte(s) - string(CONCAT CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE - "\"${CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS}\"" - " -format=p1689" - " --" - " \"\" "#" - " -x c++ -c -o " - " -MT " - " -MD -MF " - " > ") - set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FORMAT "clang") - set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FLAG "@") - elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - # https://gitlab.kitware.com/cmake/cmake/-/blob/master/.gitlab/ci/cxx_modules_rules_gcc.cmake - string(CONCAT CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE - " -E -x c++ " - " -MT -MD -MF " - " -fmodules-ts "#-fdep-file= -fdep-output= -fdep-format=trtbd" - " -o ") - set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FORMAT "gcc") - set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FLAG "-fmodules-ts -fmodule-mapper= -fdep-format=trtbd -x c++") - else () - message(FATAL_ERROR "Unsupported compilers: ${CMAKE_CXX_COMPILER}") - endif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") -endif (${PROJECT_NAME_PREFIX}MODULE) - -# ======================= -# MODULE SOURCE -# ======================= - -if (${${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED}) - set( - ${PROJECT_NAME_PREFIX}MODULE_SOURCE_CHARS_ICELAKE - - ${PROJECT_SOURCE_DIR}/src/chars/icelake.ixx - ${PROJECT_SOURCE_DIR}/src/chars/icelake_ascii.ixx - ${PROJECT_SOURCE_DIR}/src/chars/icelake_utf8.ixx - ${PROJECT_SOURCE_DIR}/src/chars/icelake_utf16.ixx - ${PROJECT_SOURCE_DIR}/src/chars/icelake_utf32.ixx - ) -else() - set( - ${PROJECT_NAME_PREFIX}MODULE_SOURCE_CHARS_ICELAKE - ) -endif (${${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED}) - -set( - ${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC - - # ========================= - # CHARS - # ========================= - - ${PROJECT_SOURCE_DIR}/src/chars/encoding.ixx - ${PROJECT_SOURCE_DIR}/src/chars/scalar.ixx - ${PROJECT_SOURCE_DIR}/src/chars/scalar_ascii.ixx - ${PROJECT_SOURCE_DIR}/src/chars/scalar_utf8.ixx - ${PROJECT_SOURCE_DIR}/src/chars/scalar_utf16.ixx - ${PROJECT_SOURCE_DIR}/src/chars/scalar_utf32.ixx - ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_CHARS_ICELAKE} - - ${PROJECT_SOURCE_DIR}/src/chars/chars.ixx - - # ========================= - # COMMAND_LINE_PARSER - # ========================= - - ${PROJECT_SOURCE_DIR}/src/command_line_parser/command_line_parser.ixx - - # ========================= - # CONCURRENCY - # ========================= - - ${PROJECT_SOURCE_DIR}/src/concurrency/thread.ixx - ${PROJECT_SOURCE_DIR}/src/concurrency/unfair_mutex.ixx - - ${PROJECT_SOURCE_DIR}/src/concurrency/concurrency.ixx - - # ========================= - # COROUTINE - # ========================= - - ${PROJECT_SOURCE_DIR}/src/coroutine/task.ixx - ${PROJECT_SOURCE_DIR}/src/coroutine/generator.ixx - - ${PROJECT_SOURCE_DIR}/src/coroutine/coroutine.ixx - - # ========================= - # ERROR - # ========================= - - ${PROJECT_SOURCE_DIR}/src/error/exception.ixx - ${PROJECT_SOURCE_DIR}/src/error/debug.ixx - ${PROJECT_SOURCE_DIR}/src/error/platform.ixx - ${PROJECT_SOURCE_DIR}/src/error/command_line.ixx - ${PROJECT_SOURCE_DIR}/src/error/instruction_set.ixx - - ${PROJECT_SOURCE_DIR}/src/error/error.ixx - - # ========================= - # FUNCTIONAL - # ========================= - - ${PROJECT_SOURCE_DIR}/src/functional/type_list.ixx - ${PROJECT_SOURCE_DIR}/src/functional/value_list.ixx - ${PROJECT_SOURCE_DIR}/src/functional/functor.ixx - ${PROJECT_SOURCE_DIR}/src/functional/aligned_union.ixx - ${PROJECT_SOURCE_DIR}/src/functional/function_ref.ixx - ${PROJECT_SOURCE_DIR}/src/functional/math.ixx - ${PROJECT_SOURCE_DIR}/src/functional/flag.ixx - ${PROJECT_SOURCE_DIR}/src/functional/function_signature.ixx - - ${PROJECT_SOURCE_DIR}/src/functional/functional.ixx - - # ========================= - # GUI - # ========================= - - ${PROJECT_SOURCE_DIR}/src/gui/draw_list.ixx - ${PROJECT_SOURCE_DIR}/src/gui/font.ixx - - ${PROJECT_SOURCE_DIR}/src/gui/gui.ixx +set(${PROJECT_NAME_PREFIX}ROOT_PATH_CMAKE_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/scripts) +set(${PROJECT_NAME_PREFIX}ROOT_PATH_EXTERNAL_LIBRARY ${CMAKE_CURRENT_SOURCE_DIR}/external) - # ========================= - # MEMORY - # ========================= - - ${PROJECT_SOURCE_DIR}/src/memory/read_write.ixx - - ${PROJECT_SOURCE_DIR}/src/memory/memory.ixx - - # ========================= - # META - # ========================= - - ${PROJECT_SOURCE_DIR}/src/meta/string.ixx - ${PROJECT_SOURCE_DIR}/src/meta/name.ixx - ${PROJECT_SOURCE_DIR}/src/meta/enumeration.ixx - ${PROJECT_SOURCE_DIR}/src/meta/member.ixx - ${PROJECT_SOURCE_DIR}/src/meta/to_string.ixx - - ${PROJECT_SOURCE_DIR}/src/meta/meta.ixx - - # ========================= - # NUMERIC - # ========================= - - ${PROJECT_SOURCE_DIR}/src/numeric/random_engine.ixx - ${PROJECT_SOURCE_DIR}/src/numeric/random.ixx - - ${PROJECT_SOURCE_DIR}/src/numeric/numeric.ixx - - # ========================= - # PRIMITIVE - # ========================= - - ${PROJECT_SOURCE_DIR}/src/primitive/multidimensional.ixx - ${PROJECT_SOURCE_DIR}/src/primitive/point.ixx - ${PROJECT_SOURCE_DIR}/src/primitive/extent.ixx - ${PROJECT_SOURCE_DIR}/src/primitive/rect.ixx - ${PROJECT_SOURCE_DIR}/src/primitive/circle.ixx - ${PROJECT_SOURCE_DIR}/src/primitive/ellipse.ixx - ${PROJECT_SOURCE_DIR}/src/primitive/color.ixx - ${PROJECT_SOURCE_DIR}/src/primitive/vertex.ixx - - ${PROJECT_SOURCE_DIR}/src/primitive/primitive.ixx - - # ========================= - # STATE_MACHINE - # ========================= - - ${PROJECT_SOURCE_DIR}/src/state_machine/state_machine.ixx - - # ========================= - # STRING - # ========================= - - ${PROJECT_SOURCE_DIR}/src/string/charconv.ixx - ${PROJECT_SOURCE_DIR}/src/string/string_pool.ixx - - ${PROJECT_SOURCE_DIR}/src/string/string.ixx - - # ========================= - # UNIT_TEST - # ========================= - - ${PROJECT_SOURCE_DIR}/src/unit_test/unit_test.ixx - - # ========================= - # WILDCARD - # ========================= - - ${PROJECT_SOURCE_DIR}/src/wildcard/wildcard.ixx - - # ========================= - # ROOT - # ========================= - - ${PROJECT_SOURCE_DIR}/src/prometheus.ixx -) - -set( - ${PROJECT_NAME_PREFIX}MODULE_SOURCE_PRIVATE - - # ========================= - # CONCURRENCY - # ========================= - - ${PROJECT_SOURCE_DIR}/src/concurrency/thread.impl.ixx - ${PROJECT_SOURCE_DIR}/src/concurrency/unfair_mutex.impl.ixx - - # ========================= - # ERROR - # ========================= - - ${PROJECT_SOURCE_DIR}/src/error/debug.impl.ixx - ${PROJECT_SOURCE_DIR}/src/error/platform.impl.ixx - ${PROJECT_SOURCE_DIR}/src/error/command_line.impl.ixx - ${PROJECT_SOURCE_DIR}/src/error/instruction_set.impl.ixx - - # ========================= - # GUI - # ========================= - - ${PROJECT_SOURCE_DIR}/src/gui/font.impl.ixx -) - -if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set_source_files_properties( - ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PRIVATE} - PROPERTIES - LANGUAGE CXX - ) -endif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - -if (${PROJECT_NAME_PREFIX}MODULE) - target_sources( - ${PROJECT_NAME} - PUBLIC - FILE_SET public_module_files - TYPE CXX_MODULES - BASE_DIRS "${PROJECT_SOURCE_DIR}" - FILES - - ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC} - ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PRIVATE} - ) -else () - target_sources( - ${PROJECT_NAME} - PUBLIC - FILE_SET public_header_files - TYPE HEADERS - BASE_DIRS "${PROJECT_SOURCE_DIR}" - FILES - - ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC} - ) - - target_sources( - ${PROJECT_NAME} - PRIVATE - - ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PRIVATE} - ) -endif (${PROJECT_NAME_PREFIX}MODULE) - -target_sources( - ${PROJECT_NAME} - PUBLIC - FILE_SET macro_header_files - TYPE HEADERS - BASE_DIRS "${PROJECT_SOURCE_DIR}/src" - FILES - - ${PROJECT_SOURCE_DIR}/src/prometheus/macro.hpp -) - -# SET FLAGS -target_compile_options( - ${PROJECT_NAME} - PUBLIC - - ${${PROJECT_NAME_PREFIX}COMPILE_FLAGS} - # __VA_OPT__ - $<$:-Zc:preprocessor> -) - -# SET DEFINITIONS -if (${PROJECT_NAME_PREFIX}MODULE) - set(${PROJECT_NAME_PREFIX}MODULE_MACRO_VARIABLE_USE_MODULE 1) -else () - set(${PROJECT_NAME_PREFIX}MODULE_MACRO_VARIABLE_USE_MODULE 0) -endif (${PROJECT_NAME_PREFIX}MODULE) - -target_compile_definitions( - ${PROJECT_NAME} - PUBLIC - - ${PROJECT_NAME_PREFIX}MAJOR_VERSION=${${PROJECT_NAME_PREFIX}MAJOR_VERSION} - ${PROJECT_NAME_PREFIX}MINOR_VERSION=${${PROJECT_NAME_PREFIX}MINOR_VERSION} - ${PROJECT_NAME_PREFIX}PATCH_VERSION=${${PROJECT_NAME_PREFIX}PATCH_VERSION} - ${PROJECT_NAME_PREFIX}VERSION="${${PROJECT_NAME_PREFIX}VERSION}" - ${PROJECT_NAME_PREFIX}COMPILER_ID="${${PROJECT_NAME_PREFIX}COMPILER_ID}" - ${PROJECT_NAME_PREFIX}COMPILER_VERSION="${${PROJECT_NAME_PREFIX}COMPILER_VERSION}" - ${PROJECT_NAME_PREFIX}COMPILER_NAME="${${PROJECT_NAME_PREFIX}COMPILER_NAME}" - ${${PROJECT_NAME_PREFIX}PLATFORM} - - ${PROJECT_NAME_PREFIX}USE_MODULE=${${PROJECT_NAME_PREFIX}MODULE_MACRO_VARIABLE_USE_MODULE} - - ${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED=${${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED} - - # Tool macros for platform determination. - $<$:${PROJECT_NAME_PREFIX}COMPILER_MSVC> - $<$:${PROJECT_NAME_PREFIX}COMPILER_GNU> - # clang-cl - $<$,$>:${PROJECT_NAME_PREFIX}COMPILER_CLANG_CL> - # clang - $<$,$>>:${PROJECT_NAME_PREFIX}COMPILER_CLANG> - # apple clang - $<$:${PROJECT_NAME_PREFIX}COMPILER_APPLE_CLANG> -) - -# SET FEATURES -target_compile_features( - ${PROJECT_NAME} - PRIVATE - cxx_std_23 -) - -link_3rd_library_freetype(${PROJECT_NAME}) -link_3rd_library_stb(${PROJECT_NAME}) - -# SET DEBUG POSTFIX ==> lib${PROJECT_NAME} -> Release lib${PROJECT}d -> Debug -set(${PROJECT_NAME_PREFIX}DEBUG_POSTFIX d CACHE STRING "Debug library postfix.") -set_target_properties( - ${PROJECT_NAME} - PROPERTIES - VERSION ${${PROJECT_NAME_PREFIX}VERSION} - SOVERSION ${${PROJECT_NAME_PREFIX}MAJOR_VERSION} - PUBLIC_HEADER "${${PROJECT_NAME_PREFIX}HEADER}" - DEBUG_POSTFIX "${${PROJECT_NAME_PREFIX}DEBUG_POSTFIX}") +include(${CMAKE_CURRENT_SOURCE_DIR}/scripts/common.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/scripts/platform_and_compiler.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/scripts/cpu_feature.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/scripts/external_installer.cmake) -# INSTALL TARGETS -#if (${PROJECT_NAME_PREFIX}INSTALL) -# # PackageProject.cmake will be used to make our target installable -# CPMAddPackage("gh:TheLartians/PackageProject.cmake@1.10.0") -# -# # the location where the project's version header will be placed should match the project's regular -# # header paths -# string(TOLOWER ${PROJECT_NAME}/version.h VERSION_HEADER_LOCATION) -# -# packageProject( -# # the name of the target to export -# NAME ${PROJECT_NAME} -# # the version of the target to export -# VERSION ${PROJECT_VERSION} -# # (optional) install your library with a namespace (Note: do NOT add extra '::') -# NAMESPACE ${PROJECT_NAME} -# # a temporary directory to create the config files -# BINARY_DIR ${PROJECT_BINARY_DIR} -# # location of the target's public headers -# # see target_include_directories -> BUILD_INTERFACE -# INCLUDE_DIR ${PROJECT_SOURCE_DIR}/src -# # should match the target's INSTALL_INTERFACE include directory -# # see target_include_directories -> INSTALL_INTERFACE -# INCLUDE_DESTINATION ${${PROJECT_NAME_PREFIX}INSTALL_HEADERS}/${PROJECT_NAME}-${PROJECT_VERSION} -# # (optional) create a header containing the version info -# # Note: that the path to headers should be lowercase -# VERSION_HEADER "${VERSION_HEADER_LOCATION}" -# # (optional) define the project's version compatibility, defaults to `AnyNewerVersion` -# # supported values: `AnyNewerVersion|SameMajorVersion|SameMinorVersion|ExactVersion` -# COMPATIBILITY AnyNewerVersion -# # semicolon separated list of the project's dependencies -# # see `LINK 3rd-PARTY LIBRARIES` -# DEPENDENCIES ${${PROJECT_NAME_PREFIX}3RD_PARTY_DEPENDENCIES} -# # (optional) option to disable the versioning of install destinations -# DISABLE_VERSION_SUFFIX YES -# # (optional) option to ignore target architecture for package resolution -# # defaults to YES for header only (i.e. INTERFACE) libraries -# ARCH_INDEPENDENT YES -# ) -#endif (${PROJECT_NAME_PREFIX}INSTALL) +# build library +include(${CMAKE_CURRENT_SOURCE_DIR}/scripts/library.cmake) # DOCUMENTS if (${PROJECT_NAME_PREFIX}DOC) diff --git a/CMakePresets.json b/CMakePresets.json index 41cbfa18..6758e011 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -45,6 +45,28 @@ } }, + { + "name": "windows-base-gcc", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_C_COMPILER": "gcc.exe", + "CMAKE_CXX_COMPILER": "g++.exe" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { + "hostOS": [ "Windows" ] + } + } + }, + { "name": "linux-base-gcc", "hidden": true, @@ -169,6 +191,43 @@ } }, + { + "name": "win-x64-Debug-GCC", + "displayName": "Windows x64 Debug GCC", + "inherits": "windows-base-gcc", + "architecture": { + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "win-x64-Release-GCC", + "displayName": "Windows x64 Release GCC", + "inherits": "win-x64-Debug-GCC", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "win-x64-MinSizeRel-GCC", + "displayName": "Windows x64 MinSizeRel GCC", + "inherits": "win-x64-Debug-GCC", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "MinSizeRel" + } + }, + { + "name": "win-x64-RelWithDebInfo-GCC", + "displayName": "Windows x64 RelWithDebInfo GCC", + "inherits": "win-x64-Debug-GCC", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + }, + { "name": "linux-x64-Debug-GCC", "displayName": "Linux x64 Debug GCC", diff --git a/LICENSE b/LICENSE index d8fb9f8d..fa41b3c4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ ISC License -Copyright (c) 2023 Life4Gal +Copyright (c) 2025 Life4Gal Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -12,4 +12,4 @@ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file +PERFORMANCE OF THIS SOFTWARE. diff --git a/cmake_utils/CPM.cmake b/cmake_utils/CPM.cmake deleted file mode 100644 index 4661b4d3..00000000 --- a/cmake_utils/CPM.cmake +++ /dev/null @@ -1,1115 +0,0 @@ -# CPM.cmake - CMake's missing package manager -# =========================================== -# See https://github.com/cpm-cmake/CPM.cmake for usage and update instructions. -# -# MIT License -# ----------- -#[[ - Copyright (c) 2019-2022 Lars Melchior and contributors - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -]] - -cmake_minimum_required(VERSION 3.14 FATAL_ERROR) - -set(CURRENT_CPM_VERSION 0.36.0) - -get_filename_component(CPM_CURRENT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" REALPATH) -if(CPM_DIRECTORY) - if(NOT CPM_DIRECTORY STREQUAL CPM_CURRENT_DIRECTORY) - if(CPM_VERSION VERSION_LESS CURRENT_CPM_VERSION) - message( - AUTHOR_WARNING - "${CPM_INDENT} \ -A dependency is using a more recent CPM version (${CURRENT_CPM_VERSION}) than the current project (${CPM_VERSION}). \ -It is recommended to upgrade CPM to the most recent version. \ -See https://github.com/cpm-cmake/CPM.cmake for more information." - ) - endif() - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - include(FetchContent) - endif() - return() - endif() - - get_property( - CPM_INITIALIZED GLOBAL "" - PROPERTY CPM_INITIALIZED - SET - ) - if(CPM_INITIALIZED) - return() - endif() -endif() - -if(CURRENT_CPM_VERSION MATCHES "development-version") - message(WARNING "Your project is using an unstable development version of CPM.cmake. \ -Please update to a recent release if possible. \ -See https://github.com/cpm-cmake/CPM.cmake for details." - ) -endif() - -set_property(GLOBAL PROPERTY CPM_INITIALIZED true) - -macro(cpm_set_policies) - # the policy allows us to change options without caching - cmake_policy(SET CMP0077 NEW) - set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) - - # the policy allows us to change set(CACHE) without caching - if(POLICY CMP0126) - cmake_policy(SET CMP0126 NEW) - set(CMAKE_POLICY_DEFAULT_CMP0126 NEW) - endif() - - # The policy uses the download time for timestamp, instead of the timestamp in the archive. This - # allows for proper rebuilds when a projects url changes - if(POLICY CMP0135) - cmake_policy(SET CMP0135 NEW) - set(CMAKE_POLICY_DEFAULT_CMP0135 NEW) - endif() -endmacro() -cpm_set_policies() - -option(CPM_USE_LOCAL_PACKAGES "Always try to use `find_package` to get dependencies" - $ENV{CPM_USE_LOCAL_PACKAGES} - ) -option(CPM_LOCAL_PACKAGES_ONLY "Only use `find_package` to get dependencies" - $ENV{CPM_LOCAL_PACKAGES_ONLY} - ) -option(CPM_DOWNLOAD_ALL "Always download dependencies from source" $ENV{CPM_DOWNLOAD_ALL}) -option(CPM_DONT_UPDATE_MODULE_PATH "Don't update the module path to allow using find_package" - $ENV{CPM_DONT_UPDATE_MODULE_PATH} - ) -option(CPM_DONT_CREATE_PACKAGE_LOCK "Don't create a package lock file in the binary path" - $ENV{CPM_DONT_CREATE_PACKAGE_LOCK} - ) -option(CPM_INCLUDE_ALL_IN_PACKAGE_LOCK - "Add all packages added through CPM.cmake to the package lock" - $ENV{CPM_INCLUDE_ALL_IN_PACKAGE_LOCK} - ) -option(CPM_USE_NAMED_CACHE_DIRECTORIES - "Use additional directory of package name in cache on the most nested level." - $ENV{CPM_USE_NAMED_CACHE_DIRECTORIES} - ) - -set(CPM_VERSION - ${CURRENT_CPM_VERSION} - CACHE INTERNAL "" - ) -set(CPM_DIRECTORY - ${CPM_CURRENT_DIRECTORY} - CACHE INTERNAL "" - ) -set(CPM_FILE - ${CMAKE_CURRENT_LIST_FILE} - CACHE INTERNAL "" - ) -set(CPM_PACKAGES - "" - CACHE INTERNAL "" - ) -set(CPM_DRY_RUN - OFF - CACHE INTERNAL "Don't download or configure dependencies (for testing)" - ) - -if(DEFINED ENV{CPM_SOURCE_CACHE}) - set(CPM_SOURCE_CACHE_DEFAULT $ENV{CPM_SOURCE_CACHE}) -else() - set(CPM_SOURCE_CACHE_DEFAULT OFF) -endif() - -set(CPM_SOURCE_CACHE - ${CPM_SOURCE_CACHE_DEFAULT} - CACHE PATH "Directory to download CPM dependencies" - ) - -if(NOT CPM_DONT_UPDATE_MODULE_PATH) - set(CPM_MODULE_PATH - "${CMAKE_BINARY_DIR}/CPM_modules" - CACHE INTERNAL "" - ) - # remove old modules - file(REMOVE_RECURSE ${CPM_MODULE_PATH}) - file(MAKE_DIRECTORY ${CPM_MODULE_PATH}) - # locally added CPM modules should override global packages - set(CMAKE_MODULE_PATH "${CPM_MODULE_PATH};${CMAKE_MODULE_PATH}") -endif() - -if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) - set(CPM_PACKAGE_LOCK_FILE - "${CMAKE_BINARY_DIR}/cpm-package-lock.cmake" - CACHE INTERNAL "" - ) - file(WRITE ${CPM_PACKAGE_LOCK_FILE} - "# CPM Package Lock\n# This file should be committed to version control\n\n" - ) -endif() - -include(FetchContent) - -# Try to infer package name from git repository uri (path or url) -function(cpm_package_name_from_git_uri URI RESULT) - if("${URI}" MATCHES "([^/:]+)/?.git/?$") - set(${RESULT} - ${CMAKE_MATCH_1} - PARENT_SCOPE - ) - else() - unset(${RESULT} PARENT_SCOPE) - endif() -endfunction() - -# Try to infer package name and version from a url -function(cpm_package_name_and_ver_from_url url outName outVer) - if(url MATCHES "[/\\?]([a-zA-Z0-9_\\.-]+)\\.(tar|tar\\.gz|tar\\.bz2|zip|ZIP)(\\?|/|$)") - # We matched an archive - set(filename "${CMAKE_MATCH_1}") - - if(filename MATCHES "([a-zA-Z0-9_\\.-]+)[_-]v?(([0-9]+\\.)*[0-9]+[a-zA-Z0-9]*)") - # We matched - (ie foo-1.2.3) - set(${outName} - "${CMAKE_MATCH_1}" - PARENT_SCOPE - ) - set(${outVer} - "${CMAKE_MATCH_2}" - PARENT_SCOPE - ) - elseif(filename MATCHES "(([0-9]+\\.)+[0-9]+[a-zA-Z0-9]*)") - # We couldn't find a name, but we found a version - # - # In many cases (which we don't handle here) the url would look something like - # `irrelevant/ACTUAL_PACKAGE_NAME/irrelevant/1.2.3.zip`. In such a case we can't possibly - # distinguish the package name from the irrelevant bits. Moreover if we try to match the - # package name from the filename, we'd get bogus at best. - unset(${outName} PARENT_SCOPE) - set(${outVer} - "${CMAKE_MATCH_1}" - PARENT_SCOPE - ) - else() - # Boldly assume that the file name is the package name. - # - # Yes, something like `irrelevant/ACTUAL_NAME/irrelevant/download.zip` will ruin our day, but - # such cases should be quite rare. No popular service does this... we think. - set(${outName} - "${filename}" - PARENT_SCOPE - ) - unset(${outVer} PARENT_SCOPE) - endif() - else() - # No ideas yet what to do with non-archives - unset(${outName} PARENT_SCOPE) - unset(${outVer} PARENT_SCOPE) - endif() -endfunction() - -# Initialize logging prefix -if(NOT CPM_INDENT) - set(CPM_INDENT - "CPM:" - CACHE INTERNAL "" - ) -endif() - -function(cpm_find_package NAME VERSION) - string(REPLACE " " ";" EXTRA_ARGS "${ARGN}") - find_package(${NAME} ${VERSION} ${EXTRA_ARGS} QUIET) - if(${CPM_ARGS_NAME}_FOUND) - if(DEFINED ${CPM_ARGS_NAME}_VERSION) - set(VERSION ${${CPM_ARGS_NAME}_VERSION}) - endif() - message(STATUS "${CPM_INDENT} using local package ${CPM_ARGS_NAME}@${VERSION}") - CPMRegisterPackage(${CPM_ARGS_NAME} "${VERSION}") - set(CPM_PACKAGE_FOUND - YES - PARENT_SCOPE - ) - else() - set(CPM_PACKAGE_FOUND - NO - PARENT_SCOPE - ) - endif() -endfunction() - -# Create a custom FindXXX.cmake module for a CPM package This prevents `find_package(NAME)` from -# finding the system library -function(cpm_create_module_file Name) - if(NOT CPM_DONT_UPDATE_MODULE_PATH) - # erase any previous modules - file(WRITE ${CPM_MODULE_PATH}/Find${Name}.cmake - "include(\"${CPM_FILE}\")\n${ARGN}\nset(${Name}_FOUND TRUE)" - ) - endif() -endfunction() - -# Find a package locally or fallback to CPMAddPackage -function(CPMFindPackage) - set(oneValueArgs NAME VERSION GIT_TAG FIND_PACKAGE_ARGUMENTS) - - cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "" ${ARGN}) - - if(NOT DEFINED CPM_ARGS_VERSION) - if(DEFINED CPM_ARGS_GIT_TAG) - cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) - endif() - endif() - - set(downloadPackage ${CPM_DOWNLOAD_ALL}) - if(DEFINED CPM_DOWNLOAD_${CPM_ARGS_NAME}) - set(downloadPackage ${CPM_DOWNLOAD_${CPM_ARGS_NAME}}) - elseif(DEFINED ENV{CPM_DOWNLOAD_${CPM_ARGS_NAME}}) - set(downloadPackage $ENV{CPM_DOWNLOAD_${CPM_ARGS_NAME}}) - endif() - if(downloadPackage) - CPMAddPackage(${ARGN}) - cpm_export_variables(${CPM_ARGS_NAME}) - return() - endif() - - cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") - if(CPM_PACKAGE_ALREADY_ADDED) - cpm_export_variables(${CPM_ARGS_NAME}) - return() - endif() - - cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) - - if(NOT CPM_PACKAGE_FOUND) - CPMAddPackage(${ARGN}) - cpm_export_variables(${CPM_ARGS_NAME}) - endif() - -endfunction() - -# checks if a package has been added before -function(cpm_check_if_package_already_added CPM_ARGS_NAME CPM_ARGS_VERSION) - if("${CPM_ARGS_NAME}" IN_LIST CPM_PACKAGES) - CPMGetPackageVersion(${CPM_ARGS_NAME} CPM_PACKAGE_VERSION) - if("${CPM_PACKAGE_VERSION}" VERSION_LESS "${CPM_ARGS_VERSION}") - message( - WARNING - "${CPM_INDENT} requires a newer version of ${CPM_ARGS_NAME} (${CPM_ARGS_VERSION}) than currently included (${CPM_PACKAGE_VERSION})." - ) - endif() - cpm_get_fetch_properties(${CPM_ARGS_NAME}) - set(${CPM_ARGS_NAME}_ADDED NO) - set(CPM_PACKAGE_ALREADY_ADDED - YES - PARENT_SCOPE - ) - cpm_export_variables(${CPM_ARGS_NAME}) - else() - set(CPM_PACKAGE_ALREADY_ADDED - NO - PARENT_SCOPE - ) - endif() -endfunction() - -# Parse the argument of CPMAddPackage in case a single one was provided and convert it to a list of -# arguments which can then be parsed idiomatically. For example gh:foo/bar@1.2.3 will be converted -# to: GITHUB_REPOSITORY;foo/bar;VERSION;1.2.3 -function(cpm_parse_add_package_single_arg arg outArgs) - # Look for a scheme - if("${arg}" MATCHES "^([a-zA-Z]+):(.+)$") - string(TOLOWER "${CMAKE_MATCH_1}" scheme) - set(uri "${CMAKE_MATCH_2}") - - # Check for CPM-specific schemes - if(scheme STREQUAL "gh") - set(out "GITHUB_REPOSITORY;${uri}") - set(packageType "git") - elseif(scheme STREQUAL "gl") - set(out "GITLAB_REPOSITORY;${uri}") - set(packageType "git") - elseif(scheme STREQUAL "bb") - set(out "BITBUCKET_REPOSITORY;${uri}") - set(packageType "git") - # A CPM-specific scheme was not found. Looks like this is a generic URL so try to determine - # type - elseif(arg MATCHES ".git/?(@|#|$)") - set(out "GIT_REPOSITORY;${arg}") - set(packageType "git") - else() - # Fall back to a URL - set(out "URL;${arg}") - set(packageType "archive") - - # We could also check for SVN since FetchContent supports it, but SVN is so rare these days. - # We just won't bother with the additional complexity it will induce in this function. SVN is - # done by multi-arg - endif() - else() - if(arg MATCHES ".git/?(@|#|$)") - set(out "GIT_REPOSITORY;${arg}") - set(packageType "git") - else() - # Give up - message(FATAL_ERROR "CPM: Can't determine package type of '${arg}'") - endif() - endif() - - # For all packages we interpret @... as version. Only replace the last occurrence. Thus URIs - # containing '@' can be used - string(REGEX REPLACE "@([^@]+)$" ";VERSION;\\1" out "${out}") - - # Parse the rest according to package type - if(packageType STREQUAL "git") - # For git repos we interpret #... as a tag or branch or commit hash - string(REGEX REPLACE "#([^#]+)$" ";GIT_TAG;\\1" out "${out}") - elseif(packageType STREQUAL "archive") - # For archives we interpret #... as a URL hash. - string(REGEX REPLACE "#([^#]+)$" ";URL_HASH;\\1" out "${out}") - # We don't try to parse the version if it's not provided explicitly. cpm_get_version_from_url - # should do this at a later point - else() - # We should never get here. This is an assertion and hitting it means there's a bug in the code - # above. A packageType was set, but not handled by this if-else. - message(FATAL_ERROR "CPM: Unsupported package type '${packageType}' of '${arg}'") - endif() - - set(${outArgs} - ${out} - PARENT_SCOPE - ) -endfunction() - -# Check that the working directory for a git repo is clean -function(cpm_check_git_working_dir_is_clean repoPath gitTag isClean) - - find_package(Git REQUIRED) - - if(NOT GIT_EXECUTABLE) - # No git executable, assume directory is clean - set(${isClean} - TRUE - PARENT_SCOPE - ) - return() - endif() - - # check for uncommitted changes - execute_process( - COMMAND ${GIT_EXECUTABLE} status --porcelain - RESULT_VARIABLE resultGitStatus - OUTPUT_VARIABLE repoStatus - OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET - WORKING_DIRECTORY ${repoPath} - ) - if(resultGitStatus) - # not supposed to happen, assume clean anyway - message(WARNING "Calling git status on folder ${repoPath} failed") - set(${isClean} - TRUE - PARENT_SCOPE - ) - return() - endif() - - if(NOT "${repoStatus}" STREQUAL "") - set(${isClean} - FALSE - PARENT_SCOPE - ) - return() - endif() - - # check for committed changes - execute_process( - COMMAND ${GIT_EXECUTABLE} diff -s --exit-code ${gitTag} - RESULT_VARIABLE resultGitDiff - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_QUIET - WORKING_DIRECTORY ${repoPath} - ) - - if(${resultGitDiff} EQUAL 0) - set(${isClean} - TRUE - PARENT_SCOPE - ) - else() - set(${isClean} - FALSE - PARENT_SCOPE - ) - endif() - -endfunction() - -# method to overwrite internal FetchContent properties, to allow using CPM.cmake to overload -# FetchContent calls. As these are internal cmake properties, this method should be used carefully -# and may need modification in future CMake versions. Source: -# https://github.com/Kitware/CMake/blob/dc3d0b5a0a7d26d43d6cfeb511e224533b5d188f/Modules/FetchContent.cmake#L1152 -function(cpm_override_fetchcontent contentName) - cmake_parse_arguments(PARSE_ARGV 1 arg "" "SOURCE_DIR;BINARY_DIR" "") - if(NOT "${arg_UNPARSED_ARGUMENTS}" STREQUAL "") - message(FATAL_ERROR "Unsupported arguments: ${arg_UNPARSED_ARGUMENTS}") - endif() - - string(TOLOWER ${contentName} contentNameLower) - set(prefix "_FetchContent_${contentNameLower}") - - set(propertyName "${prefix}_sourceDir") - define_property( - GLOBAL - PROPERTY ${propertyName} - BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" - FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" - ) - set_property(GLOBAL PROPERTY ${propertyName} "${arg_SOURCE_DIR}") - - set(propertyName "${prefix}_binaryDir") - define_property( - GLOBAL - PROPERTY ${propertyName} - BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" - FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" - ) - set_property(GLOBAL PROPERTY ${propertyName} "${arg_BINARY_DIR}") - - set(propertyName "${prefix}_populated") - define_property( - GLOBAL - PROPERTY ${propertyName} - BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" - FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" - ) - set_property(GLOBAL PROPERTY ${propertyName} TRUE) -endfunction() - -# Download and add a package from source -function(CPMAddPackage) - cpm_set_policies() - - list(LENGTH ARGN argnLength) - if(argnLength EQUAL 1) - cpm_parse_add_package_single_arg("${ARGN}" ARGN) - - # The shorthand syntax implies EXCLUDE_FROM_ALL - set(ARGN "${ARGN};EXCLUDE_FROM_ALL;YES") - endif() - - set(oneValueArgs - NAME - FORCE - VERSION - GIT_TAG - DOWNLOAD_ONLY - GITHUB_REPOSITORY - GITLAB_REPOSITORY - BITBUCKET_REPOSITORY - GIT_REPOSITORY - SOURCE_DIR - DOWNLOAD_COMMAND - FIND_PACKAGE_ARGUMENTS - NO_CACHE - GIT_SHALLOW - EXCLUDE_FROM_ALL - SOURCE_SUBDIR - ) - - set(multiValueArgs URL OPTIONS) - - cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}") - - # Set default values for arguments - - if(NOT DEFINED CPM_ARGS_VERSION) - if(DEFINED CPM_ARGS_GIT_TAG) - cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) - endif() - endif() - - if(CPM_ARGS_DOWNLOAD_ONLY) - set(DOWNLOAD_ONLY ${CPM_ARGS_DOWNLOAD_ONLY}) - else() - set(DOWNLOAD_ONLY NO) - endif() - - if(DEFINED CPM_ARGS_GITHUB_REPOSITORY) - set(CPM_ARGS_GIT_REPOSITORY "https://github.com/${CPM_ARGS_GITHUB_REPOSITORY}.git") - elseif(DEFINED CPM_ARGS_GITLAB_REPOSITORY) - set(CPM_ARGS_GIT_REPOSITORY "https://gitlab.com/${CPM_ARGS_GITLAB_REPOSITORY}.git") - elseif(DEFINED CPM_ARGS_BITBUCKET_REPOSITORY) - set(CPM_ARGS_GIT_REPOSITORY "https://bitbucket.org/${CPM_ARGS_BITBUCKET_REPOSITORY}.git") - endif() - - if(DEFINED CPM_ARGS_GIT_REPOSITORY) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_REPOSITORY ${CPM_ARGS_GIT_REPOSITORY}) - if(NOT DEFINED CPM_ARGS_GIT_TAG) - set(CPM_ARGS_GIT_TAG v${CPM_ARGS_VERSION}) - endif() - - # If a name wasn't provided, try to infer it from the git repo - if(NOT DEFINED CPM_ARGS_NAME) - cpm_package_name_from_git_uri(${CPM_ARGS_GIT_REPOSITORY} CPM_ARGS_NAME) - endif() - endif() - - set(CPM_SKIP_FETCH FALSE) - - if(DEFINED CPM_ARGS_GIT_TAG) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_TAG ${CPM_ARGS_GIT_TAG}) - # If GIT_SHALLOW is explicitly specified, honor the value. - if(DEFINED CPM_ARGS_GIT_SHALLOW) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW ${CPM_ARGS_GIT_SHALLOW}) - endif() - endif() - - if(DEFINED CPM_ARGS_URL) - # If a name or version aren't provided, try to infer them from the URL - list(GET CPM_ARGS_URL 0 firstUrl) - cpm_package_name_and_ver_from_url(${firstUrl} nameFromUrl verFromUrl) - # If we fail to obtain name and version from the first URL, we could try other URLs if any. - # However multiple URLs are expected to be quite rare, so for now we won't bother. - - # If the caller provided their own name and version, they trump the inferred ones. - if(NOT DEFINED CPM_ARGS_NAME) - set(CPM_ARGS_NAME ${nameFromUrl}) - endif() - if(NOT DEFINED CPM_ARGS_VERSION) - set(CPM_ARGS_VERSION ${verFromUrl}) - endif() - - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS URL "${CPM_ARGS_URL}") - endif() - - # Check for required arguments - - if(NOT DEFINED CPM_ARGS_NAME) - message( - FATAL_ERROR - "CPM: 'NAME' was not provided and couldn't be automatically inferred for package added with arguments: '${ARGN}'" - ) - endif() - - # Check if package has been added before - cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") - if(CPM_PACKAGE_ALREADY_ADDED) - cpm_export_variables(${CPM_ARGS_NAME}) - return() - endif() - - # Check for manual overrides - if(NOT CPM_ARGS_FORCE AND NOT "${CPM_${CPM_ARGS_NAME}_SOURCE}" STREQUAL "") - set(PACKAGE_SOURCE ${CPM_${CPM_ARGS_NAME}_SOURCE}) - set(CPM_${CPM_ARGS_NAME}_SOURCE "") - CPMAddPackage( - NAME "${CPM_ARGS_NAME}" - SOURCE_DIR "${PACKAGE_SOURCE}" - EXCLUDE_FROM_ALL "${CPM_ARGS_EXCLUDE_FROM_ALL}" - OPTIONS "${CPM_ARGS_OPTIONS}" - SOURCE_SUBDIR "${CPM_ARGS_SOURCE_SUBDIR}" - DOWNLOAD_ONLY "${DOWNLOAD_ONLY}" - FORCE True - ) - cpm_export_variables(${CPM_ARGS_NAME}) - return() - endif() - - # Check for available declaration - if(NOT CPM_ARGS_FORCE AND NOT "${CPM_DECLARATION_${CPM_ARGS_NAME}}" STREQUAL "") - set(declaration ${CPM_DECLARATION_${CPM_ARGS_NAME}}) - set(CPM_DECLARATION_${CPM_ARGS_NAME} "") - CPMAddPackage(${declaration}) - cpm_export_variables(${CPM_ARGS_NAME}) - # checking again to ensure version and option compatibility - cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") - return() - endif() - - if(CPM_USE_LOCAL_PACKAGES OR CPM_LOCAL_PACKAGES_ONLY) - cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) - - if(CPM_PACKAGE_FOUND) - cpm_export_variables(${CPM_ARGS_NAME}) - return() - endif() - - if(CPM_LOCAL_PACKAGES_ONLY) - message( - SEND_ERROR - "CPM: ${CPM_ARGS_NAME} not found via find_package(${CPM_ARGS_NAME} ${CPM_ARGS_VERSION})" - ) - endif() - endif() - - CPMRegisterPackage("${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}") - - if(DEFINED CPM_ARGS_GIT_TAG) - set(PACKAGE_INFO "${CPM_ARGS_GIT_TAG}") - elseif(DEFINED CPM_ARGS_SOURCE_DIR) - set(PACKAGE_INFO "${CPM_ARGS_SOURCE_DIR}") - else() - set(PACKAGE_INFO "${CPM_ARGS_VERSION}") - endif() - - if(DEFINED FETCHCONTENT_BASE_DIR) - # respect user's FETCHCONTENT_BASE_DIR if set - set(CPM_FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR}) - else() - set(CPM_FETCHCONTENT_BASE_DIR ${CMAKE_BINARY_DIR}/_deps) - endif() - - if(DEFINED CPM_ARGS_DOWNLOAD_COMMAND) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS DOWNLOAD_COMMAND ${CPM_ARGS_DOWNLOAD_COMMAND}) - elseif(DEFINED CPM_ARGS_SOURCE_DIR) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${CPM_ARGS_SOURCE_DIR}) - if(NOT IS_ABSOLUTE ${CPM_ARGS_SOURCE_DIR}) - # Expand `CPM_ARGS_SOURCE_DIR` relative path. This is important because EXISTS doesn't work - # for relative paths. - get_filename_component( - source_directory ${CPM_ARGS_SOURCE_DIR} REALPATH BASE_DIR ${CMAKE_CURRENT_BINARY_DIR} - ) - else() - set(source_directory ${CPM_ARGS_SOURCE_DIR}) - endif() - if(NOT EXISTS ${source_directory}) - string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) - # remove timestamps so CMake will re-download the dependency - file(REMOVE_RECURSE "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild") - endif() - elseif(CPM_SOURCE_CACHE AND NOT CPM_ARGS_NO_CACHE) - string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) - set(origin_parameters ${CPM_ARGS_UNPARSED_ARGUMENTS}) - list(SORT origin_parameters) - if(CPM_USE_NAMED_CACHE_DIRECTORIES) - string(SHA1 origin_hash "${origin_parameters};NEW_CACHE_STRUCTURE_TAG") - set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}/${CPM_ARGS_NAME}) - else() - string(SHA1 origin_hash "${origin_parameters}") - set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}) - endif() - # Expand `download_directory` relative path. This is important because EXISTS doesn't work for - # relative paths. - get_filename_component(download_directory ${download_directory} ABSOLUTE) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${download_directory}) - if(EXISTS ${download_directory}) - cpm_store_fetch_properties( - ${CPM_ARGS_NAME} "${download_directory}" - "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-build" - ) - cpm_get_fetch_properties("${CPM_ARGS_NAME}") - - if(DEFINED CPM_ARGS_GIT_TAG AND NOT (PATCH_COMMAND IN_LIST CPM_ARGS_UNPARSED_ARGUMENTS)) - # warn if cache has been changed since checkout - cpm_check_git_working_dir_is_clean(${download_directory} ${CPM_ARGS_GIT_TAG} IS_CLEAN) - if(NOT ${IS_CLEAN}) - message(WARNING "Cache for ${CPM_ARGS_NAME} (${download_directory}) is dirty") - endif() - endif() - - cpm_add_subdirectory( - "${CPM_ARGS_NAME}" "${DOWNLOAD_ONLY}" - "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" "${${CPM_ARGS_NAME}_BINARY_DIR}" - "${CPM_ARGS_EXCLUDE_FROM_ALL}" "${CPM_ARGS_OPTIONS}" - ) - set(PACKAGE_INFO "${PACKAGE_INFO} at ${download_directory}") - - # As the source dir is already cached/populated, we override the call to FetchContent. - set(CPM_SKIP_FETCH TRUE) - cpm_override_fetchcontent( - "${lower_case_name}" SOURCE_DIR "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" - BINARY_DIR "${${CPM_ARGS_NAME}_BINARY_DIR}" - ) - - else() - # Enable shallow clone when GIT_TAG is not a commit hash. Our guess may not be accurate, but - # it should guarantee no commit hash get mis-detected. - if(NOT DEFINED CPM_ARGS_GIT_SHALLOW) - cpm_is_git_tag_commit_hash("${CPM_ARGS_GIT_TAG}" IS_HASH) - if(NOT ${IS_HASH}) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW TRUE) - endif() - endif() - - # remove timestamps so CMake will re-download the dependency - file(REMOVE_RECURSE ${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild) - set(PACKAGE_INFO "${PACKAGE_INFO} to ${download_directory}") - endif() - endif() - - cpm_create_module_file(${CPM_ARGS_NAME} "CPMAddPackage(\"${ARGN}\")") - - if(CPM_PACKAGE_LOCK_ENABLED) - if((CPM_ARGS_VERSION AND NOT CPM_ARGS_SOURCE_DIR) OR CPM_INCLUDE_ALL_IN_PACKAGE_LOCK) - cpm_add_to_package_lock(${CPM_ARGS_NAME} "${ARGN}") - elseif(CPM_ARGS_SOURCE_DIR) - cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "local directory") - else() - cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "${ARGN}") - endif() - endif() - - message( - STATUS "${CPM_INDENT} adding package ${CPM_ARGS_NAME}@${CPM_ARGS_VERSION} (${PACKAGE_INFO})" - ) - - if(NOT CPM_SKIP_FETCH) - cpm_declare_fetch( - "${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}" "${PACKAGE_INFO}" "${CPM_ARGS_UNPARSED_ARGUMENTS}" - ) - cpm_fetch_package("${CPM_ARGS_NAME}" populated) - if(${populated}) - cpm_add_subdirectory( - "${CPM_ARGS_NAME}" "${DOWNLOAD_ONLY}" - "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" "${${CPM_ARGS_NAME}_BINARY_DIR}" - "${CPM_ARGS_EXCLUDE_FROM_ALL}" "${CPM_ARGS_OPTIONS}" - ) - endif() - cpm_get_fetch_properties("${CPM_ARGS_NAME}") - endif() - - set(${CPM_ARGS_NAME}_ADDED YES) - cpm_export_variables("${CPM_ARGS_NAME}") -endfunction() - -# Fetch a previously declared package -macro(CPMGetPackage Name) - if(DEFINED "CPM_DECLARATION_${Name}") - CPMAddPackage(NAME ${Name}) - else() - message(SEND_ERROR "Cannot retrieve package ${Name}: no declaration available") - endif() -endmacro() - -# export variables available to the caller to the parent scope expects ${CPM_ARGS_NAME} to be set -macro(cpm_export_variables name) - set(${name}_SOURCE_DIR - "${${name}_SOURCE_DIR}" - PARENT_SCOPE - ) - set(${name}_BINARY_DIR - "${${name}_BINARY_DIR}" - PARENT_SCOPE - ) - set(${name}_ADDED - "${${name}_ADDED}" - PARENT_SCOPE - ) - set(CPM_LAST_PACKAGE_NAME - "${name}" - PARENT_SCOPE - ) -endmacro() - -# declares a package, so that any call to CPMAddPackage for the package name will use these -# arguments instead. Previous declarations will not be overridden. -macro(CPMDeclarePackage Name) - if(NOT DEFINED "CPM_DECLARATION_${Name}") - set("CPM_DECLARATION_${Name}" "${ARGN}") - endif() -endmacro() - -function(cpm_add_to_package_lock Name) - if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) - cpm_prettify_package_arguments(PRETTY_ARGN false ${ARGN}) - file(APPEND ${CPM_PACKAGE_LOCK_FILE} "# ${Name}\nCPMDeclarePackage(${Name}\n${PRETTY_ARGN})\n") - endif() -endfunction() - -function(cpm_add_comment_to_package_lock Name) - if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) - cpm_prettify_package_arguments(PRETTY_ARGN true ${ARGN}) - file(APPEND ${CPM_PACKAGE_LOCK_FILE} - "# ${Name} (unversioned)\n# CPMDeclarePackage(${Name}\n${PRETTY_ARGN}#)\n" - ) - endif() -endfunction() - -# includes the package lock file if it exists and creates a target `cpm-update-package-lock` to -# update it -macro(CPMUsePackageLock file) - if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) - get_filename_component(CPM_ABSOLUTE_PACKAGE_LOCK_PATH ${file} ABSOLUTE) - if(EXISTS ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) - include(${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) - endif() - if(NOT TARGET cpm-update-package-lock) - add_custom_target( - cpm-update-package-lock COMMAND ${CMAKE_COMMAND} -E copy ${CPM_PACKAGE_LOCK_FILE} - ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH} - ) - endif() - set(CPM_PACKAGE_LOCK_ENABLED true) - endif() -endmacro() - -# registers a package that has been added to CPM -function(CPMRegisterPackage PACKAGE VERSION) - list(APPEND CPM_PACKAGES ${PACKAGE}) - set(CPM_PACKAGES - ${CPM_PACKAGES} - CACHE INTERNAL "" - ) - set("CPM_PACKAGE_${PACKAGE}_VERSION" - ${VERSION} - CACHE INTERNAL "" - ) -endfunction() - -# retrieve the current version of the package to ${OUTPUT} -function(CPMGetPackageVersion PACKAGE OUTPUT) - set(${OUTPUT} - "${CPM_PACKAGE_${PACKAGE}_VERSION}" - PARENT_SCOPE - ) -endfunction() - -# declares a package in FetchContent_Declare -function(cpm_declare_fetch PACKAGE VERSION INFO) - if(${CPM_DRY_RUN}) - message(STATUS "${CPM_INDENT} package not declared (dry run)") - return() - endif() - - FetchContent_Declare(${PACKAGE} ${ARGN}) -endfunction() - -# returns properties for a package previously defined by cpm_declare_fetch -function(cpm_get_fetch_properties PACKAGE) - if(${CPM_DRY_RUN}) - return() - endif() - - set(${PACKAGE}_SOURCE_DIR - "${CPM_PACKAGE_${PACKAGE}_SOURCE_DIR}" - PARENT_SCOPE - ) - set(${PACKAGE}_BINARY_DIR - "${CPM_PACKAGE_${PACKAGE}_BINARY_DIR}" - PARENT_SCOPE - ) -endfunction() - -function(cpm_store_fetch_properties PACKAGE source_dir binary_dir) - if(${CPM_DRY_RUN}) - return() - endif() - - set(CPM_PACKAGE_${PACKAGE}_SOURCE_DIR - "${source_dir}" - CACHE INTERNAL "" - ) - set(CPM_PACKAGE_${PACKAGE}_BINARY_DIR - "${binary_dir}" - CACHE INTERNAL "" - ) -endfunction() - -# adds a package as a subdirectory if viable, according to provided options -function( - cpm_add_subdirectory - PACKAGE - DOWNLOAD_ONLY - SOURCE_DIR - BINARY_DIR - EXCLUDE - OPTIONS -) - if(NOT DOWNLOAD_ONLY AND EXISTS ${SOURCE_DIR}/CMakeLists.txt) - if(EXCLUDE) - set(addSubdirectoryExtraArgs EXCLUDE_FROM_ALL) - else() - set(addSubdirectoryExtraArgs "") - endif() - if(OPTIONS) - foreach(OPTION ${OPTIONS}) - cpm_parse_option("${OPTION}") - set(${OPTION_KEY} "${OPTION_VALUE}") - endforeach() - endif() - set(CPM_OLD_INDENT "${CPM_INDENT}") - set(CPM_INDENT "${CPM_INDENT} ${PACKAGE}:") - add_subdirectory(${SOURCE_DIR} ${BINARY_DIR} ${addSubdirectoryExtraArgs}) - set(CPM_INDENT "${CPM_OLD_INDENT}") - endif() -endfunction() - -# downloads a previously declared package via FetchContent and exports the variables -# `${PACKAGE}_SOURCE_DIR` and `${PACKAGE}_BINARY_DIR` to the parent scope -function(cpm_fetch_package PACKAGE populated) - set(${populated} - FALSE - PARENT_SCOPE - ) - if(${CPM_DRY_RUN}) - message(STATUS "${CPM_INDENT} package ${PACKAGE} not fetched (dry run)") - return() - endif() - - FetchContent_GetProperties(${PACKAGE}) - - string(TOLOWER "${PACKAGE}" lower_case_name) - - if(NOT ${lower_case_name}_POPULATED) - FetchContent_Populate(${PACKAGE}) - set(${populated} - TRUE - PARENT_SCOPE - ) - endif() - - cpm_store_fetch_properties( - ${CPM_ARGS_NAME} ${${lower_case_name}_SOURCE_DIR} ${${lower_case_name}_BINARY_DIR} - ) - - set(${PACKAGE}_SOURCE_DIR - ${${lower_case_name}_SOURCE_DIR} - PARENT_SCOPE - ) - set(${PACKAGE}_BINARY_DIR - ${${lower_case_name}_BINARY_DIR} - PARENT_SCOPE - ) -endfunction() - -# splits a package option -function(cpm_parse_option OPTION) - string(REGEX MATCH "^[^ ]+" OPTION_KEY "${OPTION}") - string(LENGTH "${OPTION}" OPTION_LENGTH) - string(LENGTH "${OPTION_KEY}" OPTION_KEY_LENGTH) - if(OPTION_KEY_LENGTH STREQUAL OPTION_LENGTH) - # no value for key provided, assume user wants to set option to "ON" - set(OPTION_VALUE "ON") - else() - math(EXPR OPTION_KEY_LENGTH "${OPTION_KEY_LENGTH}+1") - string(SUBSTRING "${OPTION}" "${OPTION_KEY_LENGTH}" "-1" OPTION_VALUE) - endif() - set(OPTION_KEY - "${OPTION_KEY}" - PARENT_SCOPE - ) - set(OPTION_VALUE - "${OPTION_VALUE}" - PARENT_SCOPE - ) -endfunction() - -# guesses the package version from a git tag -function(cpm_get_version_from_git_tag GIT_TAG RESULT) - string(LENGTH ${GIT_TAG} length) - if(length EQUAL 40) - # GIT_TAG is probably a git hash - set(${RESULT} - 0 - PARENT_SCOPE - ) - else() - string(REGEX MATCH "v?([0123456789.]*).*" _ ${GIT_TAG}) - set(${RESULT} - ${CMAKE_MATCH_1} - PARENT_SCOPE - ) - endif() -endfunction() - -# guesses if the git tag is a commit hash or an actual tag or a branch name. -function(cpm_is_git_tag_commit_hash GIT_TAG RESULT) - string(LENGTH "${GIT_TAG}" length) - # full hash has 40 characters, and short hash has at least 7 characters. - if(length LESS 7 OR length GREATER 40) - set(${RESULT} - 0 - PARENT_SCOPE - ) - else() - if(${GIT_TAG} MATCHES "^[a-fA-F0-9]+$") - set(${RESULT} - 1 - PARENT_SCOPE - ) - else() - set(${RESULT} - 0 - PARENT_SCOPE - ) - endif() - endif() -endfunction() - -function(cpm_prettify_package_arguments OUT_VAR IS_IN_COMMENT) - set(oneValueArgs - NAME - FORCE - VERSION - GIT_TAG - DOWNLOAD_ONLY - GITHUB_REPOSITORY - GITLAB_REPOSITORY - GIT_REPOSITORY - SOURCE_DIR - DOWNLOAD_COMMAND - FIND_PACKAGE_ARGUMENTS - NO_CACHE - GIT_SHALLOW - ) - set(multiValueArgs OPTIONS) - cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - foreach(oneArgName ${oneValueArgs}) - if(DEFINED CPM_ARGS_${oneArgName}) - if(${IS_IN_COMMENT}) - string(APPEND PRETTY_OUT_VAR "#") - endif() - if(${oneArgName} STREQUAL "SOURCE_DIR") - string(REPLACE ${CMAKE_SOURCE_DIR} "\${CMAKE_SOURCE_DIR}" CPM_ARGS_${oneArgName} - ${CPM_ARGS_${oneArgName}} - ) - endif() - string(APPEND PRETTY_OUT_VAR " ${oneArgName} ${CPM_ARGS_${oneArgName}}\n") - endif() - endforeach() - foreach(multiArgName ${multiValueArgs}) - if(DEFINED CPM_ARGS_${multiArgName}) - if(${IS_IN_COMMENT}) - string(APPEND PRETTY_OUT_VAR "#") - endif() - string(APPEND PRETTY_OUT_VAR " ${multiArgName}\n") - foreach(singleOption ${CPM_ARGS_${multiArgName}}) - if(${IS_IN_COMMENT}) - string(APPEND PRETTY_OUT_VAR "#") - endif() - string(APPEND PRETTY_OUT_VAR " \"${singleOption}\"\n") - endforeach() - endif() - endforeach() - - if(NOT "${CPM_ARGS_UNPARSED_ARGUMENTS}" STREQUAL "") - if(${IS_IN_COMMENT}) - string(APPEND PRETTY_OUT_VAR "#") - endif() - string(APPEND PRETTY_OUT_VAR " ") - foreach(CPM_ARGS_UNPARSED_ARGUMENT ${CPM_ARGS_UNPARSED_ARGUMENTS}) - string(APPEND PRETTY_OUT_VAR " ${CPM_ARGS_UNPARSED_ARGUMENT}") - endforeach() - string(APPEND PRETTY_OUT_VAR "\n") - endif() - - set(${OUT_VAR} - ${PRETTY_OUT_VAR} - PARENT_SCOPE - ) - -endfunction() diff --git a/cmake_utils/check_cpu_features.cmake b/cmake_utils/check_cpu_features.cmake deleted file mode 100644 index 77cb1a70..00000000 --- a/cmake_utils/check_cpu_features.cmake +++ /dev/null @@ -1,27 +0,0 @@ -function(check_cpu_features feature_name) - string(TOUPPER "${feature_name}" upper_feature_name) - - try_run( - RUN_RESULT COMPILE_RESULT - SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cmake_utils/check_cpu_features_${feature_name}.cpp - - COMPILE_DEFINITIONS "-D${${PROJECT_NAME_PREFIX}PLATFORM}" - COMPILE_OUTPUT_VARIABLE COMPILE_OUTPUT_RESULT - CXX_STANDARD 23 - CXX_STANDARD_REQUIRED true - ) - - if (NOT COMPILE_RESULT) - message(FATAL_ERROR "[check_cpu_features] build failed: \n${COMPILE_OUTPUT_RESULT}") - endif (NOT COMPILE_RESULT) - - if (RUN_RESULT EQUAL 1) - message(STATUS "[check_cpu_features] '${upper_feature_name}' instruction sets are supported.") - set(${PROJECT_NAME_PREFIX}CPU_FEATURES_${upper_feature_name}_SUPPORTED 1 PARENT_SCOPE) - else() - message(STATUS "[check_cpu_features] '${upper_feature_name}' instruction sets are not supported.") - set(${PROJECT_NAME_PREFIX}CPU_FEATURES_${upper_feature_name}_SUPPORTED 0 PARENT_SCOPE) - endif (RUN_RESULT EQUAL 1) -endfunction(check_cpu_features feature_name) - -check_cpu_features(icelake) diff --git a/cmake_utils/check_cpu_features_icelake.cpp b/cmake_utils/check_cpu_features_icelake.cpp deleted file mode 100644 index bbb33534..00000000 --- a/cmake_utils/check_cpu_features_icelake.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "instruction_set.hpp" - -int main() -{ - const auto supported = detect_supported_instruction(); - - constexpr std::uint32_t required = - static_cast(InstructionSet::BMI1) | - static_cast(InstructionSet::AVX2) | - static_cast(InstructionSet::BMI2) | - static_cast(InstructionSet::AVX512BW) | - static_cast(InstructionSet::AVX512VL) | - static_cast(InstructionSet::AVX512VBMI2) | - static_cast(InstructionSet::AVX512VPOPCNTDQ); - - return (supported & required) == required; -} diff --git a/cmake_utils/cpm_install.cmake b/cmake_utils/cpm_install.cmake deleted file mode 100644 index 9a599d49..00000000 --- a/cmake_utils/cpm_install.cmake +++ /dev/null @@ -1,117 +0,0 @@ -set(CPM_INSTALL_LINK_TYPE PUBLIC PRIVATE INTERFACE) - -function( - ${PROJECT_NAME_PREFIX}cpm_pack_header_only - library_name -) - if (${library_name}_ADDED OR DEFINED ${library_name}_SOURCE_DIR) - message(STATUS "An interface library will be generated for library ${library_name}, the included headers' path is [${${library_name}_SOURCE_DIR}/include].") - add_library(${library_name} INTERFACE IMPORTED GLOBAL) - target_include_directories(${library_name} SYSTEM INTERFACE ${${library_name}_SOURCE_DIR}/include) - else () - message(FATAL_ERROR "Library ${library_name} is not installed and cannot generate target for it!") - endif (${library_name}_ADDED OR DEFINED ${library_name}_SOURCE_DIR) - - # mark it - set(${library_name}_HEADER_ONLY_GENERATED PARENT_SCOPE) -endfunction(${PROJECT_NAME_PREFIX}cpm_pack_header_only) - -function( - ${PROJECT_NAME_PREFIX}cpm_install - this_project - linked_project - link_type - # linked_target = linked_project -) - ############################################### - ############# CHECK LINK TYPE ################# - ############################################### - list(FIND CPM_INSTALL_LINK_TYPE ${link_type} link_type_index) - if (link_type_index EQUAL -1) - message(FATAL_ERROR "[link type(${link_type})] must be one of ${CPM_INSTALL_LINK_TYPE}") - endif (link_type_index EQUAL -1) - - ############################################### - ################ ADD LIBRARY ################## - ############################################### - # see CPM.cmake --> cpm_export_variables - if (${linked_project}_ADDED OR (DEFINED ${linked_project}_SOURCE_DIR AND DEFINED ${linked_project}_BINARY_DIR)) - # downloaded - message(STATUS "Successfully added [${linked_project}], the source files path is [${${linked_project}_SOURCE_DIR}], the binary files path is [${${linked_project}_BINARY_DIR}]!") - # add_subdirectory( - # ${${linked_project}_SOURCE_DIR} - # ${${linked_project}_BINARY_DIR} - # EXCLUDE_FROM_ALL - # ) - else () - # todo: https://github.com/cpm-cmake/CPM.cmake/issues/433 - message(FATAL_ERROR "Library [${linked_project}] not found!") - endif (${linked_project}_ADDED OR (DEFINED ${linked_project}_SOURCE_DIR AND DEFINED ${linked_project}_BINARY_DIR)) - - ############################################### - ########### CHECK OPTIONAL ARGS ############### - ############################################### - if (NOT (${ARGC} MATCHES 3)) - list(GET ARGN 0 linked_target) - message(STATUS "The target's name [${linked_target}] is different with the project's name [${linked_project}], the target's name will be used as the linked library name!") - else() - set(linked_target ${linked_project}) - endif (NOT (${ARGC} MATCHES 3)) - - ############################################### - ############### LINK_LIBRARY ################## - ############################################### - message(STATUS "Project [${this_project}] will [${link_type}] link [${linked_target}].") - if (link_type_index EQUAL 0) - # PUBLIC -# target_include_directories( -# ${this_project} -# SYSTEM -# PUBLIC -# ${${linked_project}_SOURCE_DIR}/include -# ) - target_link_libraries( - ${this_project} - PUBLIC - ${linked_target} - ) - elseif (link_type_index EQUAL 1) - # PRIVATE -# target_include_directories( -# ${this_project} -# SYSTEM -# PRIVATE -# ${${linked_project}_SOURCE_DIR}/include -# ) - target_link_libraries( - ${this_project} - PRIVATE - ${linked_target} - ) - elseif (link_type_index EQUAL 2) - # INTERFACE -# target_include_directories( -# ${this_project} -# SYSTEM -# INTERFACE -# ${${linked_project}_SOURCE_DIR}/include -# ) - target_link_libraries( - ${this_project} - INTERFACE - ${linked_target} - ) - else () - message(FATAL_ERROR "Impossible happened!!!Invalid link type ${link_type}.") - endif (link_type_index EQUAL 0) - - # todo: `PUBLIC` and `INTERFACE` items will populate the `INTERFACE_INCLUDE_DIRECTORIES` property of . - # If a non-PRIVATE directory include will causes vvv - # Target interface_include_directories property contains path: which is prefixed in the build directory. - target_include_directories( - ${this_project} - SYSTEM - PRIVATE - ${${linked_project}_SOURCE_DIR}/include - ) -endfunction(${PROJECT_NAME_PREFIX}cpm_install) diff --git a/cmake_utils/doc_var.cmake b/cmake_utils/doc_var.cmake deleted file mode 100644 index c3159362..00000000 --- a/cmake_utils/doc_var.cmake +++ /dev/null @@ -1,38 +0,0 @@ -include(CMakeParseArguments) - -# Sets a cache variable with a docstring joined from multiple arguments: -# set( ... CACHE ...) -# This allows splitting a long docstring for readability. -function( - ${PROJECT_NAME_PREFIX}doc_var - variable_name - variable_value - variable_type - # docstring... -) - # Joins arguments and places the results in ${result_var}. - function( - join - result_var - # args... - ) - set(result "") - foreach (arg ${ARGN}) - set(result "${result}${arg}") - endforeach () - set(${result_var} "${result}" PARENT_SCOPE) - endfunction() - - # list(GET ARGN 0 variable_name) - # list(REMOVE_AT ARGN 0) - # list(GET ARGN 0 variable_value) - # list(REMOVE_AT ARGN 0) - # # CACHE - # list(REMOVE_AT ARGN 0) - # list(GET ARGN 0 variable_type) - # list(REMOVE_AT ARGN 0) - - join(doc ${ARGN}) - - set(${variable_name} ${variable_value} CACHE ${variable_type} ${doc}) -endfunction(${PROJECT_NAME_PREFIX}doc_var) diff --git a/cmake_utils/fetch_utils.cmake b/cmake_utils/fetch_utils.cmake deleted file mode 100644 index 9908ab79..00000000 --- a/cmake_utils/fetch_utils.cmake +++ /dev/null @@ -1,35 +0,0 @@ -set( - ${PROJECT_NAME_PREFIX}cmake_utils_files - "CPM.cmake" - "cpm_install.cmake" - "doc_var.cmake" - "nuget_install.cmake" -) - -foreach (file IN LISTS ${PROJECT_NAME_PREFIX}cmake_utils_files) - set( - ${PROJECT_NAME_PREFIX}cmake_utils_files_url - "https://github.com/Life4gal/CMakeTemplateProject/tree/master/cmake_utils" - ) - - set(local_file_path ${CMAKE_CURRENT_SOURCE_DIR}/cmake_utils/${file}) - set(remote_file_path "${${PROJECT_NAME_PREFIX}cmake_utils_files_url}/${file}") - - if (NOT EXISTS ${local_file_path}) - message(STATUS "File [${local_file_path}] not exists, fetching...(${remote_file_path})") - - file( - DOWNLOAD - ${remote_file_path} - ${local_file_path} - SHOW_PROGRESS - STATUS download_result - ) - - list(GET download_result 0 error_code) - list(GET download_result 1 error_string) - if (NOT error_code EQUAL 0) - message(FATAL_ERROR "Cannot download [${remote_file_path}] ! --> ${error_string}") - endif (NOT error_code EQUAL 0) - endif (NOT EXISTS ${local_file_path}) -endforeach (file IN LISTS ${PROJECT_NAME_PREFIX}cmake_utils_files) diff --git a/cmake_utils/nuget_install.cmake b/cmake_utils/nuget_install.cmake deleted file mode 100644 index c54f48c1..00000000 --- a/cmake_utils/nuget_install.cmake +++ /dev/null @@ -1,10 +0,0 @@ -function(${PROJECT_NAME_PREFIX}nuget_install package_name config_file) - find_program(NUGET_EXE NAMES nuget REQUIRED) - - configure_file(${config_file} ${CMAKE_BINARY_DIR}/packages.${package_name}.config) - execute_process( - COMMAND - ${NUGET_EXE} restore packages.${package_name}.config -SolutionDirectory ${CMAKE_BINARY_DIR} - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) -endfunction(${PROJECT_NAME_PREFIX}nuget_install) diff --git a/3rd-party/freetype/brotlicommon.dll b/external/freetype/brotlicommon.dll similarity index 100% rename from 3rd-party/freetype/brotlicommon.dll rename to external/freetype/brotlicommon.dll diff --git a/3rd-party/freetype/brotlidec.dll b/external/freetype/brotlidec.dll similarity index 100% rename from 3rd-party/freetype/brotlidec.dll rename to external/freetype/brotlidec.dll diff --git a/3rd-party/freetype/bz2.dll b/external/freetype/bz2.dll similarity index 100% rename from 3rd-party/freetype/bz2.dll rename to external/freetype/bz2.dll diff --git a/3rd-party/freetype/freetype.cmake b/external/freetype/freetype.cmake similarity index 64% rename from 3rd-party/freetype/freetype.cmake rename to external/freetype/freetype.cmake index ccdac2ac..2ef2f547 100644 --- a/3rd-party/freetype/freetype.cmake +++ b/external/freetype/freetype.cmake @@ -1,22 +1,22 @@ -function(link_3rd_library_freetype project_name) +function(${PROJECT_NAME_PREFIX}ATTACH_EXTERNAL_FREETYPE) target_include_directories( - ${project_name} - PRIVATE - ${${PROJECT_NAME_PREFIX}3RD_PARTY_PATH}/freetype/include + ${PROJECT_NAME} + PRIVATE + ${${PROJECT_NAME_PREFIX}ROOT_PATH_EXTERNAL_LIBRARY}/freetype/include ) if (${CMAKE_SYSTEM_NAME} MATCHES "Windows") # freetype 2.13.2 - set(freetype_dll_path ${${PROJECT_NAME_PREFIX}3RD_PARTY_PATH}/freetype/freetype.dll) - set(freetype_lib_path ${${PROJECT_NAME_PREFIX}3RD_PARTY_PATH}/freetype/freetype.lib) + set(freetype_dll_path ${${PROJECT_NAME_PREFIX}ROOT_PATH_EXTERNAL_LIBRARY}/freetype/freetype.dll) + set(freetype_lib_path ${${PROJECT_NAME_PREFIX}ROOT_PATH_EXTERNAL_LIBRARY}/freetype/freetype.lib) - set(zlib1_dll_path ${${PROJECT_NAME_PREFIX}3RD_PARTY_PATH}/freetype/zlib1.dll) - set(bz2_dll_path ${${PROJECT_NAME_PREFIX}3RD_PARTY_PATH}/freetype/bz2.dll) - set(libpng16_dll_path ${${PROJECT_NAME_PREFIX}3RD_PARTY_PATH}/freetype/libpng16.dll) - set(brotlidec_dll_path ${${PROJECT_NAME_PREFIX}3RD_PARTY_PATH}/freetype/brotlidec.dll) - set(brotlicommon_path ${${PROJECT_NAME_PREFIX}3RD_PARTY_PATH}/freetype/brotlicommon.dll) + set(zlib1_dll_path ${${PROJECT_NAME_PREFIX}ROOT_PATH_EXTERNAL_LIBRARY}/freetype/zlib1.dll) + set(bz2_dll_path ${${PROJECT_NAME_PREFIX}ROOT_PATH_EXTERNAL_LIBRARY}/freetype/bz2.dll) + set(libpng16_dll_path ${${PROJECT_NAME_PREFIX}ROOT_PATH_EXTERNAL_LIBRARY}/freetype/libpng16.dll) + set(brotlidec_dll_path ${${PROJECT_NAME_PREFIX}ROOT_PATH_EXTERNAL_LIBRARY}/freetype/brotlidec.dll) + set(brotlicommon_path ${${PROJECT_NAME_PREFIX}ROOT_PATH_EXTERNAL_LIBRARY}/freetype/brotlicommon.dll) - if(NOT TARGET freetype_library) + if (NOT TARGET freetype_library) add_library( freetype_library SHARED @@ -30,16 +30,16 @@ function(link_3rd_library_freetype project_name) IMPORTED_IMPLIB ${freetype_lib_path} LINKER_LANGUAGE C ) - endif(NOT TARGET freetype_library) + endif (NOT TARGET freetype_library) get_target_property( target_binary_directory - ${project_name} + ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY ) add_custom_command( - TARGET ${project_name} + TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory ${target_binary_directory} @@ -54,7 +54,7 @@ function(link_3rd_library_freetype project_name) COMMAND ${CMAKE_COMMAND} -E echo "[freetype] Copying dll from '${libpng16_dll_path}' to '${target_binary_directory}'." COMMAND ${CMAKE_COMMAND} -E copy_if_different ${libpng16_dll_path} ${target_binary_directory}/ - + COMMAND ${CMAKE_COMMAND} -E echo "[freetype] Copying dll from '${brotlidec_dll_path}' to '${target_binary_directory}'." COMMAND ${CMAKE_COMMAND} -E copy_if_different ${brotlidec_dll_path} ${target_binary_directory}/ @@ -63,16 +63,17 @@ function(link_3rd_library_freetype project_name) ) target_link_libraries( - ${project_name} + ${PROJECT_NAME} PRIVATE freetype_library ) elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") find_package(Freetype REQUIRED) - target_link_libraries(${project_name} PRIVATE Freetype::Freetype) + target_link_libraries(${PROJECT_NAME} PRIVATE Freetype::Freetype) elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - message(FATAL_ERROR "FIXME") + find_package(Freetype REQUIRED) + target_link_libraries(${PROJECT_NAME} PRIVATE Freetype::Freetype) else () message(FATAL_ERROR "Unsupported Platform: ${CMAKE_SYSTEM_NAME}") endif (${CMAKE_SYSTEM_NAME} MATCHES "Windows") -endfunction(link_3rd_library_freetype project_name) +endfunction(${PROJECT_NAME_PREFIX}ATTACH_EXTERNAL_FREETYPE) diff --git a/3rd-party/freetype/freetype.dll b/external/freetype/freetype.dll similarity index 100% rename from 3rd-party/freetype/freetype.dll rename to external/freetype/freetype.dll diff --git a/3rd-party/freetype/freetype.lib b/external/freetype/freetype.lib similarity index 100% rename from 3rd-party/freetype/freetype.lib rename to external/freetype/freetype.lib diff --git a/3rd-party/freetype/include/freetype/config/ftconfig.h b/external/freetype/include/freetype/config/ftconfig.h similarity index 100% rename from 3rd-party/freetype/include/freetype/config/ftconfig.h rename to external/freetype/include/freetype/config/ftconfig.h diff --git a/3rd-party/freetype/include/freetype/config/ftheader.h b/external/freetype/include/freetype/config/ftheader.h similarity index 100% rename from 3rd-party/freetype/include/freetype/config/ftheader.h rename to external/freetype/include/freetype/config/ftheader.h diff --git a/3rd-party/freetype/include/freetype/config/ftmodule.h b/external/freetype/include/freetype/config/ftmodule.h similarity index 100% rename from 3rd-party/freetype/include/freetype/config/ftmodule.h rename to external/freetype/include/freetype/config/ftmodule.h diff --git a/3rd-party/freetype/include/freetype/config/ftoption.h b/external/freetype/include/freetype/config/ftoption.h similarity index 100% rename from 3rd-party/freetype/include/freetype/config/ftoption.h rename to external/freetype/include/freetype/config/ftoption.h diff --git a/3rd-party/freetype/include/freetype/config/ftstdlib.h b/external/freetype/include/freetype/config/ftstdlib.h similarity index 100% rename from 3rd-party/freetype/include/freetype/config/ftstdlib.h rename to external/freetype/include/freetype/config/ftstdlib.h diff --git a/3rd-party/freetype/include/freetype/config/integer-types.h b/external/freetype/include/freetype/config/integer-types.h similarity index 100% rename from 3rd-party/freetype/include/freetype/config/integer-types.h rename to external/freetype/include/freetype/config/integer-types.h diff --git a/3rd-party/freetype/include/freetype/config/mac-support.h b/external/freetype/include/freetype/config/mac-support.h similarity index 100% rename from 3rd-party/freetype/include/freetype/config/mac-support.h rename to external/freetype/include/freetype/config/mac-support.h diff --git a/3rd-party/freetype/include/freetype/config/public-macros.h b/external/freetype/include/freetype/config/public-macros.h similarity index 100% rename from 3rd-party/freetype/include/freetype/config/public-macros.h rename to external/freetype/include/freetype/config/public-macros.h diff --git a/3rd-party/freetype/include/freetype/freetype.h b/external/freetype/include/freetype/freetype.h similarity index 100% rename from 3rd-party/freetype/include/freetype/freetype.h rename to external/freetype/include/freetype/freetype.h diff --git a/3rd-party/freetype/include/freetype/ftadvanc.h b/external/freetype/include/freetype/ftadvanc.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftadvanc.h rename to external/freetype/include/freetype/ftadvanc.h diff --git a/3rd-party/freetype/include/freetype/ftbbox.h b/external/freetype/include/freetype/ftbbox.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftbbox.h rename to external/freetype/include/freetype/ftbbox.h diff --git a/3rd-party/freetype/include/freetype/ftbdf.h b/external/freetype/include/freetype/ftbdf.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftbdf.h rename to external/freetype/include/freetype/ftbdf.h diff --git a/3rd-party/freetype/include/freetype/ftbitmap.h b/external/freetype/include/freetype/ftbitmap.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftbitmap.h rename to external/freetype/include/freetype/ftbitmap.h diff --git a/3rd-party/freetype/include/freetype/ftbzip2.h b/external/freetype/include/freetype/ftbzip2.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftbzip2.h rename to external/freetype/include/freetype/ftbzip2.h diff --git a/3rd-party/freetype/include/freetype/ftcache.h b/external/freetype/include/freetype/ftcache.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftcache.h rename to external/freetype/include/freetype/ftcache.h diff --git a/3rd-party/freetype/include/freetype/ftchapters.h b/external/freetype/include/freetype/ftchapters.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftchapters.h rename to external/freetype/include/freetype/ftchapters.h diff --git a/3rd-party/freetype/include/freetype/ftcid.h b/external/freetype/include/freetype/ftcid.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftcid.h rename to external/freetype/include/freetype/ftcid.h diff --git a/3rd-party/freetype/include/freetype/ftcolor.h b/external/freetype/include/freetype/ftcolor.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftcolor.h rename to external/freetype/include/freetype/ftcolor.h diff --git a/3rd-party/freetype/include/freetype/ftdriver.h b/external/freetype/include/freetype/ftdriver.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftdriver.h rename to external/freetype/include/freetype/ftdriver.h diff --git a/3rd-party/freetype/include/freetype/fterrdef.h b/external/freetype/include/freetype/fterrdef.h similarity index 100% rename from 3rd-party/freetype/include/freetype/fterrdef.h rename to external/freetype/include/freetype/fterrdef.h diff --git a/3rd-party/freetype/include/freetype/fterrors.h b/external/freetype/include/freetype/fterrors.h similarity index 100% rename from 3rd-party/freetype/include/freetype/fterrors.h rename to external/freetype/include/freetype/fterrors.h diff --git a/3rd-party/freetype/include/freetype/ftfntfmt.h b/external/freetype/include/freetype/ftfntfmt.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftfntfmt.h rename to external/freetype/include/freetype/ftfntfmt.h diff --git a/3rd-party/freetype/include/freetype/ftgasp.h b/external/freetype/include/freetype/ftgasp.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftgasp.h rename to external/freetype/include/freetype/ftgasp.h diff --git a/3rd-party/freetype/include/freetype/ftglyph.h b/external/freetype/include/freetype/ftglyph.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftglyph.h rename to external/freetype/include/freetype/ftglyph.h diff --git a/3rd-party/freetype/include/freetype/ftgxval.h b/external/freetype/include/freetype/ftgxval.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftgxval.h rename to external/freetype/include/freetype/ftgxval.h diff --git a/3rd-party/freetype/include/freetype/ftgzip.h b/external/freetype/include/freetype/ftgzip.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftgzip.h rename to external/freetype/include/freetype/ftgzip.h diff --git a/3rd-party/freetype/include/freetype/ftimage.h b/external/freetype/include/freetype/ftimage.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftimage.h rename to external/freetype/include/freetype/ftimage.h diff --git a/3rd-party/freetype/include/freetype/ftincrem.h b/external/freetype/include/freetype/ftincrem.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftincrem.h rename to external/freetype/include/freetype/ftincrem.h diff --git a/3rd-party/freetype/include/freetype/ftlcdfil.h b/external/freetype/include/freetype/ftlcdfil.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftlcdfil.h rename to external/freetype/include/freetype/ftlcdfil.h diff --git a/3rd-party/freetype/include/freetype/ftlist.h b/external/freetype/include/freetype/ftlist.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftlist.h rename to external/freetype/include/freetype/ftlist.h diff --git a/3rd-party/freetype/include/freetype/ftlogging.h b/external/freetype/include/freetype/ftlogging.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftlogging.h rename to external/freetype/include/freetype/ftlogging.h diff --git a/3rd-party/freetype/include/freetype/ftlzw.h b/external/freetype/include/freetype/ftlzw.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftlzw.h rename to external/freetype/include/freetype/ftlzw.h diff --git a/3rd-party/freetype/include/freetype/ftmac.h b/external/freetype/include/freetype/ftmac.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftmac.h rename to external/freetype/include/freetype/ftmac.h diff --git a/3rd-party/freetype/include/freetype/ftmm.h b/external/freetype/include/freetype/ftmm.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftmm.h rename to external/freetype/include/freetype/ftmm.h diff --git a/3rd-party/freetype/include/freetype/ftmodapi.h b/external/freetype/include/freetype/ftmodapi.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftmodapi.h rename to external/freetype/include/freetype/ftmodapi.h diff --git a/3rd-party/freetype/include/freetype/ftmoderr.h b/external/freetype/include/freetype/ftmoderr.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftmoderr.h rename to external/freetype/include/freetype/ftmoderr.h diff --git a/3rd-party/freetype/include/freetype/ftotval.h b/external/freetype/include/freetype/ftotval.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftotval.h rename to external/freetype/include/freetype/ftotval.h diff --git a/3rd-party/freetype/include/freetype/ftoutln.h b/external/freetype/include/freetype/ftoutln.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftoutln.h rename to external/freetype/include/freetype/ftoutln.h diff --git a/3rd-party/freetype/include/freetype/ftparams.h b/external/freetype/include/freetype/ftparams.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftparams.h rename to external/freetype/include/freetype/ftparams.h diff --git a/3rd-party/freetype/include/freetype/ftpfr.h b/external/freetype/include/freetype/ftpfr.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftpfr.h rename to external/freetype/include/freetype/ftpfr.h diff --git a/3rd-party/freetype/include/freetype/ftrender.h b/external/freetype/include/freetype/ftrender.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftrender.h rename to external/freetype/include/freetype/ftrender.h diff --git a/3rd-party/freetype/include/freetype/ftsizes.h b/external/freetype/include/freetype/ftsizes.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftsizes.h rename to external/freetype/include/freetype/ftsizes.h diff --git a/3rd-party/freetype/include/freetype/ftsnames.h b/external/freetype/include/freetype/ftsnames.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftsnames.h rename to external/freetype/include/freetype/ftsnames.h diff --git a/3rd-party/freetype/include/freetype/ftstroke.h b/external/freetype/include/freetype/ftstroke.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftstroke.h rename to external/freetype/include/freetype/ftstroke.h diff --git a/3rd-party/freetype/include/freetype/ftsynth.h b/external/freetype/include/freetype/ftsynth.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftsynth.h rename to external/freetype/include/freetype/ftsynth.h diff --git a/3rd-party/freetype/include/freetype/ftsystem.h b/external/freetype/include/freetype/ftsystem.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftsystem.h rename to external/freetype/include/freetype/ftsystem.h diff --git a/3rd-party/freetype/include/freetype/fttrigon.h b/external/freetype/include/freetype/fttrigon.h similarity index 100% rename from 3rd-party/freetype/include/freetype/fttrigon.h rename to external/freetype/include/freetype/fttrigon.h diff --git a/3rd-party/freetype/include/freetype/fttypes.h b/external/freetype/include/freetype/fttypes.h similarity index 100% rename from 3rd-party/freetype/include/freetype/fttypes.h rename to external/freetype/include/freetype/fttypes.h diff --git a/3rd-party/freetype/include/freetype/ftwinfnt.h b/external/freetype/include/freetype/ftwinfnt.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ftwinfnt.h rename to external/freetype/include/freetype/ftwinfnt.h diff --git a/3rd-party/freetype/include/freetype/otsvg.h b/external/freetype/include/freetype/otsvg.h similarity index 100% rename from 3rd-party/freetype/include/freetype/otsvg.h rename to external/freetype/include/freetype/otsvg.h diff --git a/3rd-party/freetype/include/freetype/t1tables.h b/external/freetype/include/freetype/t1tables.h similarity index 100% rename from 3rd-party/freetype/include/freetype/t1tables.h rename to external/freetype/include/freetype/t1tables.h diff --git a/3rd-party/freetype/include/freetype/ttnameid.h b/external/freetype/include/freetype/ttnameid.h similarity index 100% rename from 3rd-party/freetype/include/freetype/ttnameid.h rename to external/freetype/include/freetype/ttnameid.h diff --git a/3rd-party/freetype/include/freetype/tttables.h b/external/freetype/include/freetype/tttables.h similarity index 100% rename from 3rd-party/freetype/include/freetype/tttables.h rename to external/freetype/include/freetype/tttables.h diff --git a/3rd-party/freetype/include/freetype/tttags.h b/external/freetype/include/freetype/tttags.h similarity index 100% rename from 3rd-party/freetype/include/freetype/tttags.h rename to external/freetype/include/freetype/tttags.h diff --git a/3rd-party/freetype/include/ft2build.h b/external/freetype/include/ft2build.h similarity index 100% rename from 3rd-party/freetype/include/ft2build.h rename to external/freetype/include/ft2build.h diff --git a/3rd-party/freetype/libpng16.dll b/external/freetype/libpng16.dll similarity index 100% rename from 3rd-party/freetype/libpng16.dll rename to external/freetype/libpng16.dll diff --git a/3rd-party/freetype/zlib1.dll b/external/freetype/zlib1.dll similarity index 100% rename from 3rd-party/freetype/zlib1.dll rename to external/freetype/zlib1.dll diff --git a/3rd-party/glfw/glfw.cmake b/external/glfw/glfw.cmake similarity index 52% rename from 3rd-party/glfw/glfw.cmake rename to external/glfw/glfw.cmake index 6b32c631..4a65afb1 100644 --- a/3rd-party/glfw/glfw.cmake +++ b/external/glfw/glfw.cmake @@ -1,16 +1,16 @@ -function(link_3rd_library_glfw project_name) - CPMAddPackage( +function(${PROJECT_NAME_PREFIX}ATTACH_EXTERNAL_GLFW) + cmake_language( + CALL + ${PROJECT_NAME_PREFIX}EXTERNAL_INSTALL_CPM NAME glfw GIT_TAG 3.4 GITHUB_REPOSITORY "glfw/glfw" OPTIONS "BUILD_SHARED_LIBS OFF" "GLFW_BUILD_EXAMPLES OFF" "GLFW_BUILD_TESTS OFF" "GLFW_BUILD_DOCS OFF" "GLFW_INSTALL OFF" ) - cmake_language( - CALL - ${PROJECT_NAME_PREFIX}cpm_install - ${project_name} - glfw + target_link_libraries( + ${PROJECT_NAME} PRIVATE + glfw ) -endfunction(link_3rd_library_glfw project_name) +endfunction(${PROJECT_NAME_PREFIX}ATTACH_EXTERNAL_GLFW) diff --git a/3rd-party/stb/include/stb_image.h b/external/stb/include/stb_image.h similarity index 100% rename from 3rd-party/stb/include/stb_image.h rename to external/stb/include/stb_image.h diff --git a/3rd-party/stb/include/stb_rect_pack.h b/external/stb/include/stb_rect_pack.h similarity index 100% rename from 3rd-party/stb/include/stb_rect_pack.h rename to external/stb/include/stb_rect_pack.h diff --git a/external/stb/stb.cmake b/external/stb/stb.cmake new file mode 100644 index 00000000..87d80d51 --- /dev/null +++ b/external/stb/stb.cmake @@ -0,0 +1,7 @@ +function(${PROJECT_NAME_PREFIX}ATTACH_EXTERNAL_STB) + target_include_directories( + ${PROJECT_NAME} + PRIVATE + ${${PROJECT_NAME_PREFIX}ROOT_PATH_EXTERNAL_LIBRARY}/stb/include + ) +endfunction(${PROJECT_NAME_PREFIX}ATTACH_EXTERNAL_STB) diff --git a/scripts/common.cmake b/scripts/common.cmake new file mode 100644 index 00000000..0a9af596 --- /dev/null +++ b/scripts/common.cmake @@ -0,0 +1,2 @@ +include(GNUInstallDirs) +include(CheckCXXCompilerFlag) diff --git a/scripts/cpu_feature.cmake b/scripts/cpu_feature.cmake new file mode 100644 index 00000000..47fdc752 --- /dev/null +++ b/scripts/cpu_feature.cmake @@ -0,0 +1,29 @@ +function(check_cpu_features feature_name) + string(TOUPPER "${feature_name}" upper_feature_name) + + try_run( + RUN_RESULT COMPILE_RESULT + SOURCES + ${${PROJECT_NAME_PREFIX}ROOT_PATH_CMAKE_SCRIPT}/detect_supported_instruction.cpp + ${${PROJECT_NAME_PREFIX}ROOT_PATH_CMAKE_SCRIPT}/cpu_feature_${feature_name}.cpp + + COMPILE_DEFINITIONS "-D${${PROJECT_NAME_PREFIX}PLATFORM_NAME} -march=native" + COMPILE_OUTPUT_VARIABLE COMPILE_OUTPUT_RESULT + CXX_STANDARD 23 + CXX_STANDARD_REQUIRED true + ) + + if (NOT COMPILE_RESULT) + message(FATAL_ERROR "[PROMETHEUS] [check_cpu_features] build failed: \n${COMPILE_OUTPUT_RESULT}") + endif (NOT COMPILE_RESULT) + + if (RUN_RESULT EQUAL 1) + message(STATUS "[PROMETHEUS] [check_cpu_features] '${upper_feature_name}' instruction sets are supported.") + set(${PROJECT_NAME_PREFIX}CPU_FEATURES_${upper_feature_name}_SUPPORTED 1 PARENT_SCOPE) + else() + message(STATUS "[PROMETHEUS] [check_cpu_features] '${upper_feature_name}' instruction sets are not supported.") + set(${PROJECT_NAME_PREFIX}CPU_FEATURES_${upper_feature_name}_SUPPORTED 0 PARENT_SCOPE) + endif (RUN_RESULT EQUAL 1) +endfunction(check_cpu_features feature_name) + +check_cpu_features(icelake) diff --git a/scripts/cpu_feature_icelake.cpp b/scripts/cpu_feature_icelake.cpp new file mode 100644 index 00000000..35649b6e --- /dev/null +++ b/scripts/cpu_feature_icelake.cpp @@ -0,0 +1,36 @@ +#include + +enum class InstructionSet : std::uint32_t +{ + DEFAULT = 0b0000'0000'0000'0000, + + PCLMULQDQ = 0b0000'0000'0000'0001, + SSE42 = 0b0000'0000'0000'0010, + BMI1 = 0b0000'0000'0000'0100, + AVX2 = 0b0000'0000'0000'1000, + BMI2 = 0b0000'0000'0001'0000, + AVX512F = 0b0000'0000'0010'0000, + AVX512DQ = 0b0000'0000'0100'0000, + AVX512CD = 0b0000'0000'1000'0000, + AVX512BW = 0b0000'0001'0000'0000, + AVX512VL = 0b0000'0010'0000'0000, + AVX512VBMI2 = 0b0000'0100'0000'0000, + AVX512VPOPCNTDQ = 0b0000'1000'0000'0000, +}; +extern auto detect_supported_instruction() -> std::uint32_t; + +int main() +{ + const auto supported = detect_supported_instruction(); + + constexpr std::uint32_t required = + static_cast(InstructionSet::BMI1) | + static_cast(InstructionSet::AVX2) | + static_cast(InstructionSet::BMI2) | + static_cast(InstructionSet::AVX512BW) | + static_cast(InstructionSet::AVX512VL) | + static_cast(InstructionSet::AVX512VBMI2) | + static_cast(InstructionSet::AVX512VPOPCNTDQ); + + return (supported & required) == required; +} diff --git a/cmake_utils/instruction_set.hpp b/scripts/detect_supported_instruction.cpp similarity index 98% rename from cmake_utils/instruction_set.hpp rename to scripts/detect_supported_instruction.cpp index fff74267..869efdde 100644 --- a/cmake_utils/instruction_set.hpp +++ b/scripts/detect_supported_instruction.cpp @@ -1,9 +1,13 @@ -#pragma once - -// instruction_set.ixx + instruction_set.impl.ixx - -// instruction_set.ixx #include +#include +#include + +#if __has_include() +#include +#endif +#if __has_include() +#include +#endif enum class InstructionSet : std::uint32_t { @@ -23,17 +27,6 @@ enum class InstructionSet : std::uint32_t AVX512VPOPCNTDQ = 0b0000'1000'0000'0000, }; -// instruction_set.impl.ixx -#if __has_include() -#include -#endif -#if __has_include() -#include -#endif - -#include -#include - namespace { // @see https://www.felixcloutier.com/x86/cpuid @@ -67,7 +60,7 @@ namespace * Bit 00: FSGSBASE. Supports RDFSBASE/RDGSBASE/WRFSBASE/WRGSBASE if 1. * Bit 01: IA32_TSC_ADJUST MSR is supported if 1. * Bit 02: SGX. Supports Intel® Software Guard Extensions (Intel® SGX Extensions) if 1. - * Bit 03: BMI1. + * Bit 03: BMI1. * Bit 04: HLE. * Bit 05: AVX2. Supports Intel® Advanced Vector Extensions 2 (Intel® AVX2) if 1. * Bit 06: FDP_EXCPTN_ONLY. x87 FPU Data Pointer updated only on x87 exceptions if 1. @@ -251,7 +244,7 @@ namespace } } -inline auto detect_supported_instruction() -> std::uint32_t +auto detect_supported_instruction() -> std::uint32_t { std::uint32_t host_isa = 0; diff --git a/scripts/external_installer.cmake b/scripts/external_installer.cmake new file mode 100644 index 00000000..8663e52e --- /dev/null +++ b/scripts/external_installer.cmake @@ -0,0 +1,95 @@ +# CPM +set(LOCAL_PATH "${${PROJECT_NAME_PREFIX}ROOT_PATH_CMAKE_SCRIPT}/CPM.cmake") +function(${PROJECT_NAME_PREFIX}EXTERNAL_INSTALLER_LOAD_CPM) + set(GITHUB_URL "https://github.com/cpm-cmake/CPM.cmake") + + if (NOT DEFINED ${PROJECT_NAME_PREFIX}CPM_LATEST_VERSION) + find_program(GIT_EXE NAMES git REQUIRED) + execute_process( + COMMAND + ${GIT_EXE} ls-remote --tags --sort=-v:refname ${GITHUB_URL}.git + OUTPUT_VARIABLE TAG_LIST + ERROR_VARIABLE ERROR_MESSAGE + RESULT_VARIABLE RESULT + ) + if (RESULT EQUAL 0) + string(REGEX MATCH "v[0-9]+\\.[0-9]+\\.[0-9]+" LATEST_VERSION ${TAG_LIST}) + set( + ${PROJECT_NAME_PREFIX}CPM_LATEST_VERSION + ${LATEST_VERSION} + CACHE + STRING + "CPM Latest version." + ) + else () + message(WARNING "[PROMETHEUS] [CPM] Cannot get the latest version tag: ${ERROR_MESSAGE}") + endif (RESULT EQUAL 0) + else () + set(LATEST_VERSION ${${PROJECT_NAME_PREFIX}CPM_LATEST_VERSION}) + endif (NOT DEFINED ${PROJECT_NAME_PREFIX}CPM_LATEST_VERSION) + + # https://github.com/cpm-cmake/CPM.cmake/releases/download/v0.40.2/CPM.cmake + set(GITHUB_DOWNLOAD_URL "${GITHUB_URL}/releases/download/${LATEST_VERSION}/CPM.cmake") + + if (NOT EXISTS ${LOCAL_PATH}) + message(STATUS "[PROMETHEUS] [CPM] File `CPM.cmake` does not exist, try to download...") + if (NOT DEFINED LATEST_VERSION) + message(FATAL_ERROR "[PROMETHEUS] [CPM] Can't get the latest version tag of CPM, please check your internet connection...") + else () + message(STATUS "[PROMETHEUS] [CPM] Downloading ${GITHUB_DOWNLOAD_URL}...") + file(DOWNLOAD ${GITHUB_DOWNLOAD_URL} ${LOCAL_PATH} STATUS DOWNLOAD_STATUS) + + if (NOT DOWNLOAD_STATUS EQUAL 0) + message(FATAL_ERROR "[PROMETHEUS] [CPM] Can't download the latest version tag of CPM, please check your internet connection...") + else () + message(STATUS "[PROMETHEUS] [CPM] The file has been downloaded to `${LOCAL_PATH}`") + endif (NOT DOWNLOAD_STATUS EQUAL 0) + endif (NOT DEFINED LATEST_VERSION) + endif (NOT EXISTS ${LOCAL_PATH}) + + set(CPM_USE_LOCAL_PACKAGES ON) + include(${LOCAL_PATH}) + if (DEFINED LATEST_VERSION) + message(STATUS "[PROMETHEUS] [CPM] File `CPM.cmake` does exist, compare the version...") + if (NOT "v${CURRENT_CPM_VERSION}" STREQUAL "${LATEST_VERSION}") + message(STATUS "[PROMETHEUS] [CPM] Version mismatch(v${CURRENT_CPM_VERSION} => ${LATEST_VERSION})") + message(STATUS "[PROMETHEUS] [CPM] Downloading ${GITHUB_DOWNLOAD_URL}...") + file(DOWNLOAD ${GITHUB_DOWNLOAD_URL} ${LOCAL_PATH}.tmp STATUS DOWNLOAD_STATUS) + + if (NOT DOWNLOAD_STATUS EQUAL 0) + message(WARNING "[PROMETHEUS] [CPM] Can't download the latest version tag of CPM, use local version...") + else () + file(RENAME ${LOCAL_PATH}.tmp ${LOCAL_PATH}) + + message(STATUS "[PROMETHEUS] [CPM] The file has been downloaded to `${LOCAL_PATH}`") + # force regenerate + message(FATAL_ERROR "[PROMETHEUS] [CPM] Updated CPM version, please regenerate cmake cache...") + endif (NOT DOWNLOAD_STATUS EQUAL 0) + else () + message(STATUS "[PROMETHEUS] [CPM] The CPM current version is the latest version(v${CURRENT_CPM_VERSION}), no need to update...") + endif (NOT "v${CURRENT_CPM_VERSION}" STREQUAL "${LATEST_VERSION}") + endif (DEFINED LATEST_VERSION) +endfunction(${PROJECT_NAME_PREFIX}EXTERNAL_INSTALLER_LOAD_CPM) + +cmake_language( + CALL + ${PROJECT_NAME_PREFIX}EXTERNAL_INSTALLER_LOAD_CPM +) +macro(${PROJECT_NAME_PREFIX}EXTERNAL_INSTALL_CPM) + CPMAddPackage(${ARGN}) +endmacro(${PROJECT_NAME_PREFIX}EXTERNAL_INSTALL_CPM) + +# NUGET +function(${PROJECT_NAME_PREFIX}EXTERNAL_INSTALL_NUGET package_name config_file_path) + find_program(NUGET_EXE NAMES nuget REQUIRED) + if (NUGET_EXE_FOUND) + configure_file(${config_file} ${CMAKE_BINARY_DIR}/packages.${package_name}.config) + execute_process( + COMMAND + ${NUGET_EXE} restore packages.${package_name}.config -SolutionDirectory ${CMAKE_BINARY_DIR} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + else () + message(FATAL_ERROR "[PROMETHEUS] nuget not found!") + endif (NUGET_EXE_FOUND) +endfunction(${PROJECT_NAME_PREFIX}EXTERNAL_INSTALL_NUGET package_name config_file_path) diff --git a/scripts/library.cmake b/scripts/library.cmake new file mode 100644 index 00000000..6eeccc7b --- /dev/null +++ b/scripts/library.cmake @@ -0,0 +1,540 @@ +# =================================================================================================== +# LIBRARY & LIBRARY ALIAS + +add_library(${PROJECT_NAME} STATIC) +add_library(gal::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) + +# =================================================================================================== +# COMPILE FLAGS + +if (${PROJECT_NAME_PREFIX}COMPILER_MSVC) + set(${PROJECT_NAME_PREFIX}COMPILE_FLAGS "/D_CRT_SECURE_NO_WARNINGS") + # ==================== + # PEDANTIC + if (${PROJECT_NAME_PREFIX}PEDANTIC) + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "/W4") + else () + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "/W3") + endif (${PROJECT_NAME_PREFIX}PEDANTIC) + # ==================== + # WERROR + if (${PROJECT_NAME_PREFIX}WERROR) + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "/WX") + endif (${PROJECT_NAME_PREFIX}WERROR) + # ==================== + # ASAN + if (${PROJECT_NAME_PREFIX}ASAN) + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "/fsanitize=address") + endif (${PROJECT_NAME_PREFIX}ASAN) +elseif (${PROJECT_NAME_PREFIX}COMPILER_CLANG_CL) + set(${PROJECT_NAME_PREFIX}COMPILE_FLAGS "/D_CRT_SECURE_NO_WARNINGS") + # ==================== + # PEDANTIC + if (${PROJECT_NAME_PREFIX}PEDANTIC) + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "/W4") + else () + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "/W3") + endif (${PROJECT_NAME_PREFIX}PEDANTIC) + # ==================== + # WERROR + if (${PROJECT_NAME_PREFIX}WERROR) + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "/WX") + endif (${PROJECT_NAME_PREFIX}WERROR) + # ==================== + # ASAN + if (${PROJECT_NAME_PREFIX}ASAN) + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "/fsanitize=address") + endif (${PROJECT_NAME_PREFIX}ASAN) + + # ==================== + # CHARS + if (${${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED}) + # chars/icelake_xxx + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-march=native") + endif (${${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED}) +elseif (${PROJECT_NAME_PREFIX}COMPILER_CLANG) + set(${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-Wall") + # ==================== + # PEDANTIC + if (${PROJECT_NAME_PREFIX}PEDANTIC) + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-Wextra" "-Wpedantic") + endif (${PROJECT_NAME_PREFIX}PEDANTIC) + # ==================== + # WERROR + if (${PROJECT_NAME_PREFIX}WERROR) + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-Werror") + endif (${PROJECT_NAME_PREFIX}WERROR) + # ==================== + # ASAN + if (${PROJECT_NAME_PREFIX}ASAN) + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-fsanitize=address -fno-omit-frame-pointer") + endif (${PROJECT_NAME_PREFIX}ASAN) + + # ==================== + # CHARS + if (${${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED}) + # chars/icelake_xxx + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-march=native") + endif (${${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED}) +elseif (${PROJECT_NAME_PREFIX}COMPILER_GNU) + set(${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-Wall") + # ==================== + # PEDANTIC + if (${PROJECT_NAME_PREFIX}PEDANTIC) + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-Wextra" "-Wpedantic") + endif (${PROJECT_NAME_PREFIX}PEDANTIC) + # ==================== + # WERROR + if (${PROJECT_NAME_PREFIX}WERROR) + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-Werror") + endif (${PROJECT_NAME_PREFIX}WERROR) + # ==================== + # + # https://gcc.gnu.org/onlinedocs/gcc-12.3.0/libstdc++/manual/manual/using.html#manual.intro.using.flags + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-lstdc++_libbacktrace") + # https://gcc.gnu.org/onlinedocs/libstdc++/manual/using.html#manual.intro.using.flags + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-lstdc++exp") + # ==================== + # ASAN + if (${PROJECT_NAME_PREFIX}ASAN) + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-fsanitize=address -fno-omit-frame-pointer") + endif (${PROJECT_NAME_PREFIX}ASAN) + + # ==================== + # CHARS + if (${${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED}) + # chars/icelake_xxx + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-march=native") + endif (${${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED}) +elseif (${PROJECT_NAME_PREFIX}COMPILER_CLANG_APPLE) + set(${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-Wall") + # ==================== + # PEDANTIC + if (${PROJECT_NAME_PREFIX}PEDANTIC) + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-Wextra" "-Wpedantic") + endif (${PROJECT_NAME_PREFIX}PEDANTIC) + # ==================== + # WERROR + if (${PROJECT_NAME_PREFIX}WERROR) + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-Werror") + endif (${PROJECT_NAME_PREFIX}WERROR) + # ==================== + # ASAN + if (${PROJECT_NAME_PREFIX}ASAN) + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-fsanitize=address -fno-omit-frame-pointer") + endif (${PROJECT_NAME_PREFIX}ASAN) + + # ==================== + # CHARS + if (${${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED}) + # chars/icelake_xxx + list(APPEND ${PROJECT_NAME_PREFIX}COMPILE_FLAGS "-march=native") + endif (${${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED}) +endif (${PROJECT_NAME_PREFIX}COMPILER_MSVC) + +target_compile_options( + ${PROJECT_NAME} + PUBLIC + + ${${PROJECT_NAME_PREFIX}COMPILE_FLAGS} + # __VA_OPT__ + $<$:-Zc:preprocessor> +) + +# =================================================================================================== +# COMPILE DEFINITIONS & FEATURES + +if (${PROJECT_NAME_PREFIX}ASAN) + set(${PROJECT_NAME_PREFIX}ASAN_VALUE 1) +else () + set(${PROJECT_NAME_PREFIX}ASAN_VALUE 0) +endif (${PROJECT_NAME_PREFIX}ASAN) + +target_compile_definitions( + ${PROJECT_NAME} + PUBLIC + + ${PROJECT_NAME_PREFIX}MAJOR_VERSION=${${PROJECT_NAME_PREFIX}MAJOR_VERSION} + ${PROJECT_NAME_PREFIX}MINOR_VERSION=${${PROJECT_NAME_PREFIX}MINOR_VERSION} + ${PROJECT_NAME_PREFIX}PATCH_VERSION=${${PROJECT_NAME_PREFIX}PATCH_VERSION} + ${PROJECT_NAME_PREFIX}VERSION="${${PROJECT_NAME_PREFIX}VERSION}" + + ${PROJECT_NAME_PREFIX}VERSION_NAMESPACE_NAME=${${PROJECT_NAME_PREFIX}VERSION_NAMESPACE_NAME} + + ${PROJECT_NAME_PREFIX}COMPILER_NAME="${${PROJECT_NAME_PREFIX}COMPILER_NAME}" + ${PROJECT_NAME_PREFIX}COMPILER_VERSION="${${PROJECT_NAME_PREFIX}COMPILER_VERSION}" + ${PROJECT_NAME_PREFIX}COMPILER_FULL_NAME="${${PROJECT_NAME_PREFIX}COMPILER_FULL_NAME}" + + ${${PROJECT_NAME_PREFIX}PLATFORM_NAME} + + ${PROJECT_NAME_PREFIX}ENABLE_ASAN=${${PROJECT_NAME_PREFIX}ASAN_VALUE} + ${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED=${${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED} + + # msvc + $<$:${PROJECT_NAME_PREFIX}COMPILER_MSVC> + # g++ + $<$:${PROJECT_NAME_PREFIX}COMPILER_GNU> + # clang-cl + $<$,$>:${PROJECT_NAME_PREFIX}COMPILER_CLANG_CL> + # clang + $<$,$>>:${PROJECT_NAME_PREFIX}COMPILER_CLANG> + # apple clang + $<$:${PROJECT_NAME_PREFIX}COMPILER_APPLE_CLANG> +) + +target_compile_features( + ${PROJECT_NAME} + PRIVATE + cxx_std_23 +) + +set(${PROJECT_NAME_PREFIX}DEBUG_POSTFIX d CACHE STRING "Debug library postfix.") +set_target_properties( + ${PROJECT_NAME} + PROPERTIES + VERSION ${${PROJECT_NAME_PREFIX}VERSION} + SOVERSION ${${PROJECT_NAME_PREFIX}MAJOR_VERSION} + PUBLIC_HEADER "${${PROJECT_NAME_PREFIX}HEADER}" + DEBUG_POSTFIX "${${PROJECT_NAME_PREFIX}DEBUG_POSTFIX}" +) + +# =================================================================================================== +# LIBRARY SOURCE + +set( + ${PROJECT_NAME_PREFIX}LIBRARY_SOURCE_PUBLIC + + # ========================= + # META + # ========================= + + ${PROJECT_SOURCE_DIR}/src/meta/name.hpp + ${PROJECT_SOURCE_DIR}/src/meta/string.hpp + ${PROJECT_SOURCE_DIR}/src/meta/enumeration.hpp + ${PROJECT_SOURCE_DIR}/src/meta/member.hpp + ${PROJECT_SOURCE_DIR}/src/meta/member.visit.inl + ${PROJECT_SOURCE_DIR}/src/meta/to_string.hpp + ${PROJECT_SOURCE_DIR}/src/meta/dimension.hpp + ${PROJECT_SOURCE_DIR}/src/meta/dimension.cache.inl + + ${PROJECT_SOURCE_DIR}/src/meta/meta.hpp + + # ========================= + # PLATFORM + # ========================= + + ${PROJECT_SOURCE_DIR}/src/platform/exception.hpp + ${PROJECT_SOURCE_DIR}/src/platform/os.hpp + ${PROJECT_SOURCE_DIR}/src/platform/cpu.hpp + ${PROJECT_SOURCE_DIR}/src/platform/environment.hpp + + ${PROJECT_SOURCE_DIR}/src/platform/platform.hpp + + # ========================= + # FUNCTIONAL + # ========================= + + ${PROJECT_SOURCE_DIR}/src/functional/type_list.hpp + ${PROJECT_SOURCE_DIR}/src/functional/value_list.hpp + ${PROJECT_SOURCE_DIR}/src/functional/functor.hpp + ${PROJECT_SOURCE_DIR}/src/functional/aligned_union.hpp + ${PROJECT_SOURCE_DIR}/src/functional/function_ref.hpp + ${PROJECT_SOURCE_DIR}/src/functional/hash.hpp + ${PROJECT_SOURCE_DIR}/src/functional/enumeration.hpp + + ${PROJECT_SOURCE_DIR}/src/functional/functional.hpp + + # ========================= + # MATH + # ========================= + + ${PROJECT_SOURCE_DIR}/src/math/cmath.hpp + + ${PROJECT_SOURCE_DIR}/src/math/math.hpp + + # ========================= + # MEMORY + # ========================= + + ${PROJECT_SOURCE_DIR}/src/memory/rw.hpp + + ${PROJECT_SOURCE_DIR}/src/memory/memory.hpp + + # ========================= + # NUMERIC + # ========================= + + ${PROJECT_SOURCE_DIR}/src/numeric/random_engine.hpp + ${PROJECT_SOURCE_DIR}/src/numeric/random.hpp + + ${PROJECT_SOURCE_DIR}/src/numeric/numeric.hpp + + # ========================= + # PRIMITIVE + # ========================= + + ${PROJECT_SOURCE_DIR}/src/primitive/point.hpp + ${PROJECT_SOURCE_DIR}/src/primitive/extent.hpp + ${PROJECT_SOURCE_DIR}/src/primitive/rect.hpp + ${PROJECT_SOURCE_DIR}/src/primitive/circle.hpp + ${PROJECT_SOURCE_DIR}/src/primitive/ellipse.hpp + ${PROJECT_SOURCE_DIR}/src/primitive/color.hpp + ${PROJECT_SOURCE_DIR}/src/primitive/vertex.hpp + + ${PROJECT_SOURCE_DIR}/src/primitive/primitive.hpp + + # ========================= + # CONCURRENCY + # ========================= + + ${PROJECT_SOURCE_DIR}/src/concurrency/thread.hpp + ${PROJECT_SOURCE_DIR}/src/concurrency/queue.hpp + + ${PROJECT_SOURCE_DIR}/src/concurrency/concurrency.hpp + + # ========================= + # COROUTINE + # ========================= + + ${PROJECT_SOURCE_DIR}/src/coroutine/task.hpp + ${PROJECT_SOURCE_DIR}/src/coroutine/generator.hpp + + ${PROJECT_SOURCE_DIR}/src/coroutine/coroutine.hpp + + # ========================= + # STRING + # ========================= + + ${PROJECT_SOURCE_DIR}/src/string/charconv.hpp + ${PROJECT_SOURCE_DIR}/src/string/string_pool.hpp + + ${PROJECT_SOURCE_DIR}/src/string/string.hpp + + # ========================= + # I18N + # ========================= + + ${PROJECT_SOURCE_DIR}/src/i18n/range.hpp + + ${PROJECT_SOURCE_DIR}/src/i18n/i18n.hpp + + # ========================= + # CHARS + # ========================= + + ${PROJECT_SOURCE_DIR}/src/chars/def.hpp + + ${PROJECT_SOURCE_DIR}/src/chars/scalar.hpp + + ${PROJECT_SOURCE_DIR}/src/chars/detail/icelake.utf8.hpp + ${PROJECT_SOURCE_DIR}/src/chars/detail/icelake.utf32.hpp + ${PROJECT_SOURCE_DIR}/src/chars/icelake.hpp + + ${PROJECT_SOURCE_DIR}/src/chars/chars.hpp + + # ========================= + # COMMAND_LINE_PARSER + # ========================= + + ${PROJECT_SOURCE_DIR}/src/command_line_parser/error.hpp + ${PROJECT_SOURCE_DIR}/src/command_line_parser/regex.hpp + ${PROJECT_SOURCE_DIR}/src/command_line_parser/option.hpp + ${PROJECT_SOURCE_DIR}/src/command_line_parser/parser.hpp + + ${PROJECT_SOURCE_DIR}/src/command_line_parser/command_line_parser.hpp + + # ========================= + # UNIT_TEST + # ========================= + + ${PROJECT_SOURCE_DIR}/src/unit_test/def.hpp + ${PROJECT_SOURCE_DIR}/src/unit_test/events.hpp + ${PROJECT_SOURCE_DIR}/src/unit_test/operands.hpp + ${PROJECT_SOURCE_DIR}/src/unit_test/executor.hpp + ${PROJECT_SOURCE_DIR}/src/unit_test/dispatcher.hpp + + ${PROJECT_SOURCE_DIR}/src/unit_test/unit_test.hpp + + # ========================= + # DRAW + # ========================= + + ${PROJECT_SOURCE_DIR}/src/draw/flag.hpp + ${PROJECT_SOURCE_DIR}/src/draw/def.hpp + ${PROJECT_SOURCE_DIR}/src/draw/font.hpp + ${PROJECT_SOURCE_DIR}/src/draw/shared_data.hpp + ${PROJECT_SOURCE_DIR}/src/draw/theme.hpp + ${PROJECT_SOURCE_DIR}/src/draw/mouse.hpp + ${PROJECT_SOURCE_DIR}/src/draw/draw_list.hpp + ${PROJECT_SOURCE_DIR}/src/draw/window.hpp + ${PROJECT_SOURCE_DIR}/src/draw/context.hpp + + ${PROJECT_SOURCE_DIR}/src/draw/draw.hpp +) + +set( + ${PROJECT_NAME_PREFIX}LIBRARY_SOURCE_PRIVATE + + # ========================= + # PLATFORM + # ========================= + + ${PROJECT_SOURCE_DIR}/src/platform/exception.cpp + ${PROJECT_SOURCE_DIR}/src/platform/os.cpp + ${PROJECT_SOURCE_DIR}/src/platform/cpu.cpp + ${PROJECT_SOURCE_DIR}/src/platform/environment.cpp + + # ========================= + # CONCURRENCY + # ========================= + + ${PROJECT_SOURCE_DIR}/src/concurrency/thread.cpp + + # ========================= + # I18N + # ========================= + + ${PROJECT_SOURCE_DIR}/src/i18n/range.cpp + + # ========================= + # CHARS + # ========================= + + ${PROJECT_SOURCE_DIR}/src/chars/scalar.cpp + ${PROJECT_SOURCE_DIR}/src/chars/icelake.cpp + + # ========================= + # DRAW + # ========================= + + ${PROJECT_SOURCE_DIR}/src/draw/font.cpp + ${PROJECT_SOURCE_DIR}/src/draw/shared_data.cpp + ${PROJECT_SOURCE_DIR}/src/draw/theme.cpp + ${PROJECT_SOURCE_DIR}/src/draw/mouse.cpp + ${PROJECT_SOURCE_DIR}/src/draw/draw_list.cpp + ${PROJECT_SOURCE_DIR}/src/draw/window.cpp + ${PROJECT_SOURCE_DIR}/src/draw/context.cpp +) + +set_source_files_properties( + ${${PROJECT_NAME_PREFIX}LIBRARY_SOURCE_PUBLIC} + ${${PROJECT_NAME_PREFIX}LIBRARY_SOURCE_PRIVATE} + PROPERTIES + LANGUAGE CXX +) + +target_sources( + ${PROJECT_NAME} + PUBLIC + FILE_SET public_header_files + TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}" + FILES + + ${${PROJECT_NAME_PREFIX}LIBRARY_SOURCE_PUBLIC} +) + +target_sources( + ${PROJECT_NAME} + PRIVATE + ${${PROJECT_NAME_PREFIX}LIBRARY_SOURCE_PRIVATE} +) + +target_sources( + ${PROJECT_NAME} + PUBLIC + FILE_SET macro_header_files + TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}/src" + FILES + + ${PROJECT_SOURCE_DIR}/src/prometheus/macro.hpp +) + +# =================================================================================================== +# EXTERNAL LIBRARY + +# STB +include(${${PROJECT_NAME_PREFIX}ROOT_PATH_EXTERNAL_LIBRARY}/stb/stb.cmake) +cmake_language( + CALL + ${PROJECT_NAME_PREFIX}ATTACH_EXTERNAL_STB +) + +# FREETYPE +include(${${PROJECT_NAME_PREFIX}ROOT_PATH_EXTERNAL_LIBRARY}/freetype/freetype.cmake) +cmake_language( + CALL + ${PROJECT_NAME_PREFIX}ATTACH_EXTERNAL_FREETYPE +) + +if (${PROJECT_NAME_PREFIX}COMPILER_CLANG OR ${PROJECT_NAME_PREFIX}COMPILER_GNU) + target_link_libraries( + ${PROJECT_NAME} + PRIVATE + + # + #stdc++_libbacktrace + # + stdc++exp + ) +endif (${PROJECT_NAME_PREFIX}COMPILER_CLANG OR ${PROJECT_NAME_PREFIX}COMPILER_GNU) + +# =================================================================================================== +# LIBRARY BINARY + +# lib${PROJECT_NAME} -> Release +# lib${PROJECT}d -> Debug +set(${PROJECT_NAME_PREFIX}DEBUG_POSTFIX d CACHE STRING "Debug library postfix.") +set_target_properties( + ${PROJECT_NAME} + PROPERTIES + VERSION ${${PROJECT_NAME_PREFIX}VERSION} + SOVERSION ${${PROJECT_NAME_PREFIX}MAJOR_VERSION} + PUBLIC_HEADER "${${PROJECT_NAME_PREFIX}LIBRARY_SOURCE_PUBLIC}" + DEBUG_POSTFIX "${${PROJECT_NAME_PREFIX}DEBUG_POSTFIX}" +) + +# =================================================================================================== +# LIBRARY INSTALL + +#if (${PROJECT_NAME_PREFIX}INSTALL) +# # PackageProject.cmake will be used to make our target installable +# CPMAddPackage("gh:TheLartians/PackageProject.cmake@1.12.0") +# +# # the location where the project's version header will be placed should match the project's regular +# # header paths +# string(TOLOWER ${PROJECT_NAME}/version.h VERSION_HEADER_LOCATION) +# +# packageProject( +# # the name of the target to export +# NAME ${PROJECT_NAME} +# # the version of the target to export +# VERSION ${PROJECT_VERSION} +# # (optional) install your library with a namespace (Note: do NOT add extra '::') +# NAMESPACE ${PROJECT_NAME} +# # a temporary directory to create the config files +# BINARY_DIR ${PROJECT_BINARY_DIR} +# # location of the target's public headers +# # see target_include_directories -> BUILD_INTERFACE +# INCLUDE_DIR ${PROJECT_SOURCE_DIR}/src +# # should match the target's INSTALL_INTERFACE include directory +# # see target_include_directories -> INSTALL_INTERFACE +# INCLUDE_DESTINATION ${${PROJECT_NAME_PREFIX}INSTALL_HEADERS}/${PROJECT_NAME}-${PROJECT_VERSION} +# # (optional) option to install only header files with matching pattern +# INCLUDE_HEADER_PATTERN "*.hpp" +# # (optional) create a header containing the version info +# # Note: that the path to headers should be lowercase +# VERSION_HEADER "${VERSION_HEADER_LOCATION}" +# # (optional) define the project's version compatibility, defaults to `AnyNewerVersion` +# # supported values: `AnyNewerVersion|SameMajorVersion|SameMinorVersion|ExactVersion` +# COMPATIBILITY AnyNewerVersion +# # semicolon separated list of the project's dependencies +# DEPENDENCIES ${${PROJECT_NAME_PREFIX}EXTERNAL_DEPENDENCIES} +# # (optional) option to disable the versioning of install destinations +# DISABLE_VERSION_SUFFIX YES +# # (optional) option to ignore target architecture for package resolution +# # defaults to YES for header only (i.e. INTERFACE) libraries +# ARCH_INDEPENDENT YES +# ) +#endif (${PROJECT_NAME_PREFIX}INSTALL) diff --git a/scripts/maybe_useful/module_source.cmake b/scripts/maybe_useful/module_source.cmake new file mode 100644 index 00000000..7d4039f8 --- /dev/null +++ b/scripts/maybe_useful/module_source.cmake @@ -0,0 +1,473 @@ +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake_utils/module_workaround.cmake) + +# ======================= +# MODULE SOURCE +# ======================= + +if (${${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED}) + set( + ${PROJECT_NAME_PREFIX}MODULE_SOURCE_CHARS_ICELAKE + + ${PROJECT_SOURCE_DIR}/src/chars/icelake.ixx + ${PROJECT_SOURCE_DIR}/src/chars/icelake_ascii.ixx + ${PROJECT_SOURCE_DIR}/src/chars/icelake_utf8.ixx + ${PROJECT_SOURCE_DIR}/src/chars/icelake_utf16.ixx + ${PROJECT_SOURCE_DIR}/src/chars/icelake_utf32.ixx + ) +else () + set( + ${PROJECT_NAME_PREFIX}MODULE_SOURCE_CHARS_ICELAKE + ) +endif (${${PROJECT_NAME_PREFIX}CPU_FEATURES_ICELAKE_SUPPORTED}) + +set( + ${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC + + # ========================= + # META + # ========================= + + ${PROJECT_SOURCE_DIR}/src/meta/string.ixx + ${PROJECT_SOURCE_DIR}/src/meta/name.ixx + ${PROJECT_SOURCE_DIR}/src/meta/enumeration.ixx + ${PROJECT_SOURCE_DIR}/src/meta/member.ixx + ${PROJECT_SOURCE_DIR}/src/meta/to_string.ixx + + ${PROJECT_SOURCE_DIR}/src/meta/meta.ixx + + # ========================= + # PLATFORM + # ========================= + + ${PROJECT_SOURCE_DIR}/src/platform/exception.ixx + ${PROJECT_SOURCE_DIR}/src/platform/debug.ixx + ${PROJECT_SOURCE_DIR}/src/platform/command_line.ixx + ${PROJECT_SOURCE_DIR}/src/platform/instruction_set.ixx + + ${PROJECT_SOURCE_DIR}/src/platform/platform.ixx + + # ========================= + # FUNCTIONAL + # ========================= + + ${PROJECT_SOURCE_DIR}/src/functional/type_list.ixx + ${PROJECT_SOURCE_DIR}/src/functional/value_list.ixx + ${PROJECT_SOURCE_DIR}/src/functional/functor.ixx + ${PROJECT_SOURCE_DIR}/src/functional/aligned_union.ixx + ${PROJECT_SOURCE_DIR}/src/functional/function_ref.ixx + ${PROJECT_SOURCE_DIR}/src/functional/math.ixx + ${PROJECT_SOURCE_DIR}/src/functional/flag.ixx + ${PROJECT_SOURCE_DIR}/src/functional/function_signature.ixx + ${PROJECT_SOURCE_DIR}/src/functional/hash.ixx + + ${PROJECT_SOURCE_DIR}/src/functional/functional.ixx + + # ========================= + # MEMORY + # ========================= + + ${PROJECT_SOURCE_DIR}/src/memory/read_write.ixx + + ${PROJECT_SOURCE_DIR}/src/memory/memory.ixx + + # ========================= + # NUMERIC + # ========================= + + ${PROJECT_SOURCE_DIR}/src/numeric/random_engine.ixx + ${PROJECT_SOURCE_DIR}/src/numeric/random.ixx + + ${PROJECT_SOURCE_DIR}/src/numeric/numeric.ixx + + # ========================= + # PRIMITIVE + # ========================= + + ${PROJECT_SOURCE_DIR}/src/primitive/multidimensional.ixx + ${PROJECT_SOURCE_DIR}/src/primitive/point.ixx + ${PROJECT_SOURCE_DIR}/src/primitive/extent.ixx + ${PROJECT_SOURCE_DIR}/src/primitive/rect.ixx + ${PROJECT_SOURCE_DIR}/src/primitive/circle.ixx + ${PROJECT_SOURCE_DIR}/src/primitive/ellipse.ixx + ${PROJECT_SOURCE_DIR}/src/primitive/color.ixx + ${PROJECT_SOURCE_DIR}/src/primitive/vertex.ixx + + ${PROJECT_SOURCE_DIR}/src/primitive/primitive.ixx + + # ========================= + # CONCURRENCY + # ========================= + + ${PROJECT_SOURCE_DIR}/src/concurrency/thread.ixx + ${PROJECT_SOURCE_DIR}/src/concurrency/unfair_mutex.ixx + ${PROJECT_SOURCE_DIR}/src/concurrency/queue.ixx + + ${PROJECT_SOURCE_DIR}/src/concurrency/concurrency.ixx + + # ========================= + # COROUTINE + # ========================= + + ${PROJECT_SOURCE_DIR}/src/coroutine/task.ixx + ${PROJECT_SOURCE_DIR}/src/coroutine/generator.ixx + + ${PROJECT_SOURCE_DIR}/src/coroutine/coroutine.ixx + + # ========================= + # STRING + # ========================= + + ${PROJECT_SOURCE_DIR}/src/string/charconv.ixx + ${PROJECT_SOURCE_DIR}/src/string/string_pool.ixx + + ${PROJECT_SOURCE_DIR}/src/string/string.ixx + + # ========================= + # IO + # ========================= + + ${PROJECT_SOURCE_DIR}/src/io/device.ixx + + ${PROJECT_SOURCE_DIR}/src/io/io.ixx + + # ========================= + # CHARS + # ========================= + + ${PROJECT_SOURCE_DIR}/src/chars/encoding.ixx + ${PROJECT_SOURCE_DIR}/src/chars/scalar.ixx + ${PROJECT_SOURCE_DIR}/src/chars/scalar_ascii.ixx + ${PROJECT_SOURCE_DIR}/src/chars/scalar_utf8.ixx + ${PROJECT_SOURCE_DIR}/src/chars/scalar_utf16.ixx + ${PROJECT_SOURCE_DIR}/src/chars/scalar_utf32.ixx + ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_CHARS_ICELAKE} + + ${PROJECT_SOURCE_DIR}/src/chars/chars.ixx + + # ========================= + # COMMAND_LINE_PARSER + # ========================= + + ${PROJECT_SOURCE_DIR}/src/command_line_parser/command_line_parser.ixx + + # ========================= + # UNIT_TEST + # ========================= + + ${PROJECT_SOURCE_DIR}/src/unit_test/unit_test.ixx + + # ========================= + # WILDCARD + # ========================= + + ${PROJECT_SOURCE_DIR}/src/wildcard/wildcard.ixx + + # ========================= + # STATE_MACHINE + # ========================= + + ${PROJECT_SOURCE_DIR}/src/state_machine/state_machine.ixx + + # ========================= + # DRAW + # ========================= + + ${PROJECT_SOURCE_DIR}/src/draw/def.ixx + ${PROJECT_SOURCE_DIR}/src/draw/font.ixx + ${PROJECT_SOURCE_DIR}/src/draw/draw_list.ixx + ${PROJECT_SOURCE_DIR}/src/draw/theme.ixx + ${PROJECT_SOURCE_DIR}/src/draw/window.ixx + ${PROJECT_SOURCE_DIR}/src/draw/context.ixx + + ${PROJECT_SOURCE_DIR}/src/draw/draw.ixx + + # ========================= + # ROOT + # ========================= + + ${PROJECT_SOURCE_DIR}/src/prometheus.ixx +) + +set( + ${PROJECT_NAME_PREFIX}MODULE_SOURCE_PRIVATE + + # ========================= + # PLATFORM + # ========================= + + ${PROJECT_SOURCE_DIR}/src/platform/exception.impl.ixx + ${PROJECT_SOURCE_DIR}/src/platform/debug.impl.ixx + ${PROJECT_SOURCE_DIR}/src/platform/command_line.impl.ixx + ${PROJECT_SOURCE_DIR}/src/platform/instruction_set.impl.ixx + + # ========================= + # CONCURRENCY + # ========================= + + ${PROJECT_SOURCE_DIR}/src/concurrency/thread.impl.ixx + ${PROJECT_SOURCE_DIR}/src/concurrency/unfair_mutex.impl.ixx + + # ========================= + # DRAW + # ========================= + + ${PROJECT_SOURCE_DIR}/src/draw/def.impl.ixx + ${PROJECT_SOURCE_DIR}/src/draw/font.impl.ixx + ${PROJECT_SOURCE_DIR}/src/draw/draw_list.impl.ixx + ${PROJECT_SOURCE_DIR}/src/draw/theme.impl.ixx + ${PROJECT_SOURCE_DIR}/src/draw/window.impl.ixx + ${PROJECT_SOURCE_DIR}/src/draw/context.impl.ixx +) + +set(${PROJECT_NAME_PREFIX}MODULE_FRAGMENT_MACRO_NAME "${PROJECT_NAME_PREFIX}MODULE_FRAGMENT_DEFINED") +# --------------------------------- +# #if not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED +# ... +# #endif not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED <---- WARNING!!! +# --------------------------------- +if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + if (CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC") + target_compile_options( + ${PROJECT_NAME} + PUBLIC + "-Wno-extra-tokens" + ) + else () + target_compile_options( + ${PROJECT_NAME} + PUBLIC + "-Wno-endif-labels" + ) + endif (CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC") +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options( + ${PROJECT_NAME} + PUBLIC + "-Wno-endif-labels" + ) +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + target_compile_options( + ${PROJECT_NAME} + PUBLIC + "-Wno-endif-labels" + ) +else () +endif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + +if (${PROJECT_NAME_PREFIX}MODULE) + target_compile_definitions( + ${PROJECT_NAME} + PUBLIC + ${PROJECT_NAME_PREFIX}USE_MODULE=1 + ) + + # MSVC => + # xxx.impl.ixx + # #if GAL_PROMETHEUS_USE_MODULE + # module; <== warning C5201: a module declaration can appear only at the start of a translation unit unless a global module fragment is used + # #include <...> + # + # export import xxx.impl; + # + # import ...; + # + #else + # #include <...> + # + # #endif + + function(MODIFY_MODULE_FILE file sources) + file(RELATIVE_PATH relative_path ${PROJECT_SOURCE_DIR}/src ${file}) + set(target_path ${${PROJECT_NAME_PREFIX}TEMP_CXX_MODULE_PATH}/${relative_path}) + + string(REPLACE "/" "_" cache_var_name ${relative_path}) + string(TOUPPER ${cache_var_name} cache_var_name) + set(cache_var_name "${PROJECT_NAME_PREFIX}MODULE_FILE_CACHED_${cache_var_name}") + file(MD5 ${file} cache_var_md5) + + if (NOT DEFINED ${cache_var_name} OR NOT ${${cache_var_name}} STREQUAL ${cache_var_md5}) + message(STATUS "[PROMETHEUS] [MODULE FILE FRAGMENT] ${file} => ${target_path}") + + get_filename_component(dir ${target_path} DIRECTORY) + file(MAKE_DIRECTORY ${dir}) + + file(READ ${file} file_content LIMIT 2048) + set(module_fragment_begin "#if not ${${PROJECT_NAME_PREFIX}MODULE_FRAGMENT_MACRO_NAME}") + set(module_fragment_end "#endif not ${${PROJECT_NAME_PREFIX}MODULE_FRAGMENT_MACRO_NAME}") + string(FIND "${file_content}" "${module_fragment_begin}" fragment_begin) + string(FIND "${file_content}" "${module_fragment_end}" fragment_end) + if (fragment_begin EQUAL -1 OR fragment_end EQUAL -1) + message( + FATAL_ERROR + "File [${file}] does not contain MODULE fragment. \n\t\"${module_fragment_begin}\" => ${fragment_begin}\n\t\"${module_fragment_end}\" => ${fragment_end}" + ) + endif (fragment_begin EQUAL -1 OR fragment_end EQUAL -1) + + string(LENGTH ${module_fragment_begin} module_fragment_begin_length) + math(EXPR real_fragment_begin "${fragment_begin} + ${module_fragment_begin_length}") + math(EXPR fragment_length "${fragment_end} - ${real_fragment_begin}") + string(SUBSTRING "${file_content}" ${real_fragment_begin} ${fragment_length} module_fragment) + + file(WRITE ${target_path} "module;") + file(APPEND ${target_path} "${module_fragment}") + file(APPEND ${target_path} "#define ${${PROJECT_NAME_PREFIX}MODULE_FRAGMENT_MACRO_NAME} 1\n") + + # warning C5244: '#include ' in the purview of module 'gal.prometheus' appears erroneous. + # Consider moving that directive before the module declaration, or replace the textual inclusion with 'import ;'. + file(APPEND ${target_path} "${PROJECT_NAME_PREFIX}COMPILER_DISABLE_WARNING_PUSH\n") + file(APPEND ${target_path} "#if defined(${PROJECT_NAME_PREFIX}COMPILER_MSVC)\n") + file(APPEND ${target_path} "${PROJECT_NAME_PREFIX}COMPILER_DISABLE_WARNING(5244)\n") + file(APPEND ${target_path} "#endif defined(${PROJECT_NAME_PREFIX}COMPILER_MSVC)\n") + file(APPEND ${target_path} "#include <${relative_path}>\n") + file(APPEND ${target_path} "${PROJECT_NAME_PREFIX}COMPILER_DISABLE_WARNING_POP\n") + + set(${cache_var_name} ${cache_var_md5} CACHE STRING "MD5 of ${file}'s module fragment." FORCE) + endif (NOT DEFINED ${cache_var_name} OR NOT ${${cache_var_name}} STREQUAL ${cache_var_md5}) + + list(APPEND ${sources} ${target_path}) + set(${sources} ${${sources}} PARENT_SCOPE) + endfunction(MODIFY_MODULE_FILE file sources) + + # PUBLIC + set(${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC_ORIGINAL ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC}) + set(${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC "") + + foreach (file ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC_ORIGINAL}) + MODIFY_MODULE_FILE(${file} ${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC) + endforeach (file ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC_ORIGINAL}) + + # PRIVATE + set(${PROJECT_NAME_PREFIX}MODULE_SOURCE_PRIVATE_ORIGINAL ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PRIVATE}) + set(${PROJECT_NAME_PREFIX}MODULE_SOURCE_PRIVATE "") + + foreach (file ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PRIVATE_ORIGINAL}) + MODIFY_MODULE_FILE(${file} ${PROJECT_NAME_PREFIX}MODULE_SOURCE_PRIVATE) + endforeach (file ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PRIVATE_ORIGINAL}) +else () + set(${PROJECT_NAME_PREFIX}USE_MODULE 0) + target_compile_definitions( + ${PROJECT_NAME} + PUBLIC + ${PROJECT_NAME_PREFIX}USE_MODULE=0 + ${${PROJECT_NAME_PREFIX}MODULE_FRAGMENT_MACRO_NAME}=1 + ) +endif (${PROJECT_NAME_PREFIX}MODULE) + +if (${PROJECT_NAME_PREFIX}MODULE) + target_sources( + ${PROJECT_NAME} + PUBLIC + FILE_SET public_module_files + TYPE CXX_MODULES + #BASE_DIRS "${PROJECT_SOURCE_DIR}" + BASE_DIRS "${${PROJECT_NAME_PREFIX}TEMP_CXX_MODULE_PATH}" + FILES + + ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC} + ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PRIVATE} + ) +else () + set_source_files_properties( + ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC} + ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PRIVATE} + PROPERTIES + LANGUAGE CXX + ) + set_source_files_properties( + ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC} + PROPERTIES + HEADER_FILE_ONLY ON + ) + + target_sources( + ${PROJECT_NAME} + PUBLIC + FILE_SET public_header_files + TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}" + FILES + + ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC} + ) + + target_sources( + ${PROJECT_NAME} + #PUBLIC + #${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC} + PRIVATE + ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PRIVATE} + ) + + #function(MODIFY_MODULE_FILE file sources) + # file(RELATIVE_PATH relative_path ${PROJECT_SOURCE_DIR}/src ${file}) + # # remove suffix + # string(REGEX REPLACE "\\.[^.]*$" "" relative_path_no_suffix ${relative_path}) + + # get_filename_component(dir ${relative_path_no_suffix} DIRECTORY) + # get_filename_component(filename ${relative_path_no_suffix} NAME) + + # #string(LENGTH "${dir}" dir_length) + # #if (dir_length EQUAL 0 OR dir STREQUAL filename) + # string(REPLACE "/" "_" cache_var_name "${relative_path_no_suffix}.hpp") + # string(TOUPPER ${cache_var_name} cache_var_name) + # set(cache_var_name "${PROJECT_NAME_PREFIX}HEADER_FILE_CACHED_${cache_var_name}") + # file(MD5 ${file} cache_var_md5) + + # if (NOT DEFINED ${cache_var_name} OR NOT ${${cache_var_name}} STREQUAL ${cache_var_md5}) + # set(target_path "${${PROJECT_NAME_PREFIX}TEMP_CXX_MODULE_PATH}/${relative_path_no_suffix}.hpp") + # get_filename_component(target_dir ${target_path} DIRECTORY) + # file(MAKE_DIRECTORY ${target_dir}) + + # message(STATUS "[PROMETHEUS] [MODULE FILE FRAGMENT] ${file} => ${target_path}") + + # file(WRITE ${target_path} "#pragma once\n") + # file(APPEND ${target_path} "#include <${relative_path}>\n") + + # set(${cache_var_name} ${cache_var_md5} CACHE STRING "MD5 of ${file}'s module fragment." FORCE) + # endif (NOT DEFINED ${cache_var_name} OR NOT ${${cache_var_name}} STREQUAL ${cache_var_md5}) + # #endif (dir_length EQUAL 0 OR dir STREQUAL filename) + + # list(APPEND ${sources} ${target_path}) + # set(${sources} ${${sources}} PARENT_SCOPE) + #endfunction(MODIFY_MODULE_FILE file sources) + # + ## make INTELLISENSE happy :( + #set(${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC_INTELLISENSE "") + + #foreach (file ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC}) + # MODIFY_MODULE_FILE(${file} ${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC_INTELLISENSE) + #endforeach (file ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC}) + + #target_sources( + # ${PROJECT_NAME} + # PUBLIC + # FILE_SET public_intellisense_header_files + # TYPE HEADERS + # BASE_DIRS "${${PROJECT_NAME_PREFIX}TEMP_CXX_MODULE_PATH}" + # FILES + + # ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC_INTELLISENSE} + #) + + #target_sources( + # ${PROJECT_NAME} + # PUBLIC + # ${${PROJECT_NAME_PREFIX}MODULE_SOURCE_PUBLIC_INTELLISENSE} + #) +endif (${PROJECT_NAME_PREFIX}MODULE) + +target_sources( + ${PROJECT_NAME} + PUBLIC + FILE_SET macro_header_files + TYPE HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}/src" + FILES + + ${PROJECT_SOURCE_DIR}/src/prometheus/macro.hpp +) + +# 3RD PARTY LIBRARY +link_3rd_party_library_freetype(${PROJECT_NAME}) +link_3rd_party_library_stb(${PROJECT_NAME}) diff --git a/scripts/maybe_useful/module_workaround.cmake b/scripts/maybe_useful/module_workaround.cmake new file mode 100644 index 00000000..ff475f07 --- /dev/null +++ b/scripts/maybe_useful/module_workaround.cmake @@ -0,0 +1,91 @@ +set(${PROJECT_NAME_PREFIX}TEMP_CXX_MODULE_PATH ${CMAKE_BINARY_DIR}/temp_module CACHE STRING "Temp module path" FORCE) +if (${PROJECT_NAME_PREFIX}MODULE) + if (CMAKE_VERSION VERSION_LESS "3.30") + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + if (NOT DEFINED ENV{VCToolsInstallDir}) + message(FATAL_ERROR "[PROMETHEUS] Unable to find path to VC Tools in environment variables!") + endif (NOT DEFINED ENV{VCToolsInstallDir}) + + if (NOT EXISTS $ENV{VCToolsInstallDir}) + message(FATAL_ERROR "[PROMETHEUS] Invalid VC Tools installation path!") + else () + message(STATUS "[PROMETHEUS] Found VC Tools in: [$ENV{VCToolsInstallDir}]") + endif (NOT EXISTS $ENV{VCToolsInstallDir}) + + if (NOT EXISTS $ENV{VCToolsInstallDir}modules/std.ixx) + message(FATAL_ERROR "[PROMETHEUS] Cannot find std.ixx! Please check MSVC version(${CMAKE_CXX_COMPILER_VERSION})!") + else () + message(STATUS "[PROMETHEUS] Found module std.ixx in: [$ENV{VCToolsInstallDir}modules\\std.ixx]") + endif (NOT EXISTS $ENV{VCToolsInstallDir}modules/std.ixx) + + configure_file( + $ENV{VCToolsInstallDir}modules/std.ixx + ${${PROJECT_NAME_PREFIX}TEMP_CXX_MODULE_PATH}/std.ixx + COPYONLY + ) + + set_source_files_properties( + ${${PROJECT_NAME_PREFIX}TEMP_CXX_MODULE_PATH}/std.ixx + PROPERTIES + COMPILE_OPTIONS "/Wv:18" + ) + + target_sources( + ${PROJECT_NAME} + # fixme: Possible conflicts with other libraries? + PUBLIC + FILE_SET std_module + TYPE CXX_MODULES + BASE_DIRS "${PROJECT_SOURCE_DIR}" + FILES + + $<$:${${PROJECT_NAME_PREFIX}TEMP_CXX_MODULE_PATH}/std.ixx> + ) + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + # https://gitlab.kitware.com/cmake/cmake/-/blob/master/.gitlab/ci/cxx_modules_rules_clang.cmake + # Default to C++ extensions being off. Clang's modules support have trouble + # with extensions right now. + set(CMAKE_CXX_EXTENSIONS OFF) + + # fixme: https://discourse.cmake.org/t/c-20-modules-update/7330/24 + # fixme: + # PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. + # Stack dump: + # 0. Program arguments: "C:/Program Files/Microsoft Visual Studio/2022/Preview/VC/Tools/Llvm/x64/bin/clang-scan-deps.exe" -format=p1689 -- C:\\PROGRA~1\\MIB055~1\\2022\\Preview\\VC\\Tools\\Llvm\\x64\\bin\\clang-cl.exe -DGAL_PROMETHEUS_COMPILER_CLANG_CL -DGAL_PROMETHEUS_COMPILER_ID=\"Clang\" -DGAL_PROMETHEUS_COMPILER_NAME=\"Clang.16.0.5\" -DGAL_PROMETHEUS_COMPILER_VERSION=\"16.0.5\" -DGAL_PROMETHEUS_MAJOR_VERSION=0 -DGAL_PROMETHEUS_MINOR_VERSION=0 -DGAL_PROMETHEUS_PATCH_VERSION=6 -DGAL_PROMETHEUS_PLATFORM_WINDOWS -DGAL_PROMETHEUS_VERSION=\"0.0.6\" -IC:\workspace\life4gal\prometheus\src -x c++ C:\workspace\life4gal\prometheus\src\infrastructure\type_traits.ixx -c -o CMakeFiles\prometheus.dir\src\infrastructure\type_traits.ixx.obj -MT CMakeFiles\prometheus.dir\src\infrastructure\type_traits.ixx.obj.ddi -MD -MF CMakeFiles\prometheus.dir\src\infrastructure\type_traits.ixx.obj.ddi.d > CMakeFiles\prometheus.dir\src\infrastructure\type_traits.ixx.obj.ddi" + # Exception Code: 0xC0000005 + # #0 0x00007ff7b4803c75 C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\Llvm\x64\bin\clang-scan-deps.exe 0x3c75 C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\Llvm\x64\bin\clang-scan-deps.exe 0x3efc9d0 + # #1 0x00007ff7b4803c75 (C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\Llvm\x64\bin\clang-scan-deps.exe+0x3c75) + # #2 0x00007ff7b86fc9d0 (C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\Llvm\x64\bin\clang-scan-deps.exe+0x3efc9d0) + # 0x00007FF7B4803C75, C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\Llvm\x64\bin\clang-scan-deps.exe(0x00007FF7B4800000) + 0x3C75 byte(s) + # 0x00007FF7B86FC9D0, C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\Llvm\x64\bin\clang-scan-deps.exe(0x00007FF7B4800000) + 0x3EFC9D0 byte(s) + # 0x00007FFA728526AD, C:\WINDOWS\System32\KERNEL32.DLL(0x00007FFA72840000) + 0x126AD byte(s), BaseThreadInitThunk() + 0x1D byte(s) + # 0x00007FFA72C2AA68, C:\WINDOWS\SYSTEM32\ntdll.dll(0x00007FFA72BD0000) + 0x5AA68 byte(s), RtlUserThreadStart() + 0x28 byte(s) + string(CONCAT CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE + "\"${CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS}\"" + " -format=p1689" + " --" + " \"\" "#" + " -x c++ -c -o " + " -MT " + " -MD -MF " + " > ") + set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FORMAT "clang") + set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FLAG "@") + elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # https://gitlab.kitware.com/cmake/cmake/-/blob/master/.gitlab/ci/cxx_modules_rules_gcc.cmake + string(CONCAT CMAKE_EXPERIMENTAL_CXX_SCANDEP_SOURCE + " -E -x c++ " + " -MT -MD -MF " + " -fmodules-ts "#-fdep-file= -fdep-output= -fdep-format=trtbd" + " -o ") + set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FORMAT "gcc") + set(CMAKE_EXPERIMENTAL_CXX_MODULE_MAP_FLAG "-fmodules-ts -fmodule-mapper= -fdep-format=trtbd -x c++") + else () + message(FATAL_ERROR "[PROMETHEUS] Unsupported compilers: ${CMAKE_CXX_COMPILER}") + endif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + else() + # todo + #set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "0e5b6991-d74f-4b3d-a41c-cf096e0b2508") + #set(CMAKE_CXX_MODULE_STD 1) + endif (CMAKE_VERSION VERSION_LESS "3.30") +endif (${PROJECT_NAME_PREFIX}MODULE) \ No newline at end of file diff --git a/scripts/platform_and_compiler.cmake b/scripts/platform_and_compiler.cmake new file mode 100644 index 00000000..9b9d6ee2 --- /dev/null +++ b/scripts/platform_and_compiler.cmake @@ -0,0 +1,46 @@ +# -------------------------------------------------------------- +# PLATFORM + +if (${CMAKE_SYSTEM_NAME} MATCHES "Windows") + set(${PROJECT_NAME_PREFIX}PLATFORM_WINDOWS ON) + set(${PROJECT_NAME_PREFIX}PLATFORM_NAME ${PROJECT_NAME_PREFIX}PLATFORM_WINDOWS) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + set(${PROJECT_NAME_PREFIX}PLATFORM_LINUX ON) + set(${PROJECT_NAME_PREFIX}PLATFORM_NAME ${PROJECT_NAME_PREFIX}PLATFORM_LINUX) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(${PROJECT_NAME_PREFIX}PLATFORM_DARWIN ON) + set(${PROJECT_NAME_PREFIX}PLATFORM_NAME ${PROJECT_NAME_PREFIX}PLATFORM_DARWIN) +else () + message(FATAL_ERROR "[PROMETHEUS] Unknown Platform: ${CMAKE_SYSTEM_NAME}") +endif (${CMAKE_SYSTEM_NAME} MATCHES "Windows") + +# -------------------------------------------------------------- +# COMPILER + +if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set(${PROJECT_NAME_PREFIX}COMPILER_MSVC ON) + set(${PROJECT_NAME_PREFIX}COMPILER_NAME "MSVC") +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + if (CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC") + set(${PROJECT_NAME_PREFIX}COMPILER_CLANG_CL ON) + set(${PROJECT_NAME_PREFIX}COMPILER_NAME "ClangCL") + else () + set(${PROJECT_NAME_PREFIX}COMPILER_CLANG ON) + set(${PROJECT_NAME_PREFIX}COMPILER_NAME "Clang") + endif (CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC") +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(${PROJECT_NAME_PREFIX}COMPILER_GNU ON) + set(${PROJECT_NAME_PREFIX}COMPILER_NAME "GNU") +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + set(${PROJECT_NAME_PREFIX}COMPILER_CLANG_APPLE ON) + set(${PROJECT_NAME_PREFIX}COMPILER_NAME "AppleClang") +else () + message(FATAL_ERROR "[PROMETHEUS] Unknown compiler: ${CMAKE_CXX_COMPILER}") +endif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + +set(${PROJECT_NAME_PREFIX}COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION}) +set(${PROJECT_NAME_PREFIX}COMPILER_FULL_NAME ${${PROJECT_NAME_PREFIX}COMPILER_NAME}.${${PROJECT_NAME_PREFIX}COMPILER_VERSION}) + +message(STATUS "[PROMETHEUS] CMAKE VERSION: ${CMAKE_VERSION}. Compiler: ${${PROJECT_NAME_PREFIX}COMPILER_FULL_NAME}. Platform: ${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}.") +message(STATUS "[PROMETHEUS] ${PROJECT_NAME} VERSION: ${${PROJECT_NAME_PREFIX}VERSION}") +message(STATUS "[PROMETHEUS] ${PROJECT_NAME} BUILD TYPE: ${CMAKE_BUILD_TYPE}") diff --git a/src/chars/chars.hpp b/src/chars/chars.hpp new file mode 100644 index 00000000..e5e99851 --- /dev/null +++ b/src/chars/chars.hpp @@ -0,0 +1,376 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include +#include + +namespace gal::prometheus::chars +{ + namespace chars_detail + { + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + constexpr std::uint32_t icelake_required = + static_cast(platform::InstructionSet::BMI1) | + static_cast(platform::InstructionSet::AVX2) | + static_cast(platform::InstructionSet::BMI2) | + static_cast(platform::InstructionSet::AVX512BW) | + static_cast(platform::InstructionSet::AVX512VL) | + static_cast(platform::InstructionSet::AVX512VBMI2) | + static_cast(platform::InstructionSet::AVX512VPOPCNTDQ); + #endif + } + + [[nodiscard]] inline auto encoding_of(const std::span input) noexcept -> EncodingType + { + [[maybe_unused]] const auto supported = platform::detect_supported_instruction(); + + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + + if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) + { + return Icelake::encoding_of(input); + } + + #else + + #endif + + return Scalar::encoding_of(input); + } + + [[nodiscard]] inline auto encoding_of(const std::span input) noexcept -> EncodingType + { + [[maybe_unused]] const auto supported = platform::detect_supported_instruction(); + + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + + if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) + { + return Icelake::encoding_of(input); + } + + #else + + #endif + + return Scalar::encoding_of(input); + } + + template + [[nodiscard]] constexpr auto validate(const input_type_of input) noexcept -> result_error_input_type + { + [[maybe_unused]] const auto supported = platform::detect_supported_instruction(); + + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + + if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) + { + return Icelake::validate(input); + } + + #else + + #endif + + return Scalar::validate(input); + } + + template + [[nodiscard]] constexpr auto validate(const typename input_type_of::const_pointer input) noexcept -> result_error_input_type + { + [[maybe_unused]] const auto supported = platform::detect_supported_instruction(); + + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + + if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) + { + return Icelake::validate(input); + } + + #else + + #endif + + return Scalar::validate(input); + } + + template + [[nodiscard]] constexpr auto length(const input_type_of input) noexcept -> typename input_type_of::size_type + { + [[maybe_unused]] const auto supported = platform::detect_supported_instruction(); + + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + + if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) + { + return Icelake::length(input); + } + + #else + + #endif + + return Scalar::length(input); + } + + template + [[nodiscard]] constexpr auto length(const typename input_type_of::const_pointer input) noexcept -> typename input_type_of::size_type + { + [[maybe_unused]] const auto supported = platform::detect_supported_instruction(); + + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + + if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) + { + return Icelake::length(input); + } + + #else + + #endif + + return Scalar::length(input); + } + + template + [[nodiscard]] constexpr auto convert( + typename output_type_of::pointer output, + const input_type_of input + ) noexcept -> auto + { + [[maybe_unused]] const auto supported = platform::detect_supported_instruction(); + + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + + if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) + { + return Icelake::convert(output, input); + } + + #else + + #endif + + return Scalar::convert(output, input); + } + + template + [[nodiscard]] constexpr auto convert( + typename output_type_of::pointer output, + const typename input_type_of::const_pointer input + ) noexcept -> auto + { + [[maybe_unused]] const auto supported = platform::detect_supported_instruction(); + + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + + if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) + { + return Icelake::convert(output, input); + } + + #else + + #endif + + return Scalar::convert(output, input); + } + + template + [[nodiscard]] constexpr auto convert(const input_type_of input) noexcept -> StringType + { + [[maybe_unused]] const auto supported = platform::detect_supported_instruction(); + + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + + if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) + { + return Icelake::convert(input); + } + + #else + + #endif + + return Scalar::convert(input); + } + + template + [[nodiscard]] constexpr auto convert(const typename input_type_of::const_pointer input) noexcept -> StringType + { + [[maybe_unused]] const auto supported = platform::detect_supported_instruction(); + + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + + if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) + { + return Icelake::convert(input); + } + + #else + + #endif + + return Scalar::convert(input); + } + + template + [[nodiscard]] constexpr auto convert(const input_type_of input) noexcept -> std::basic_string::value_type> + { + [[maybe_unused]] const auto supported = platform::detect_supported_instruction(); + + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + + if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) + { + return Icelake::convert(input); + } + + #else + + #endif + + return Scalar::convert(input); + } + + template + [[nodiscard]] constexpr auto convert(const typename input_type_of::const_pointer input) noexcept -> std::basic_string::value_type> + { + [[maybe_unused]] const auto supported = platform::detect_supported_instruction(); + + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + + if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) + { + return Icelake::convert(input); + } + + #else + + #endif + + return Scalar::convert(input); + } + + inline auto flip( + const output_type_of::pointer output, + const input_type_of input + ) noexcept -> void + { + [[maybe_unused]] const auto supported = platform::detect_supported_instruction(); + + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + + if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) + { + return Icelake::flip(output, input); + } + + #else + + #endif + + return Scalar::flip(output, input); + } + + inline auto flip( + const output_type_of::pointer output, + const input_type_of::const_pointer input + ) noexcept -> void + { + [[maybe_unused]] const auto supported = platform::detect_supported_instruction(); + + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + + if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) + { + return Icelake::flip(output, input); + } + + #else + + #endif + + return Scalar::flip(output, input); + } + + template + constexpr auto flip(const input_type_of input) noexcept -> StringType + { + [[maybe_unused]] const auto supported = platform::detect_supported_instruction(); + + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + + if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) + { + return Icelake::flip(input); + } + + #else + + #endif + + return Scalar::flip(input); + } + + template + constexpr auto flip(const input_type_of::const_pointer input) noexcept -> StringType + { + [[maybe_unused]] const auto supported = platform::detect_supported_instruction(); + + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + + if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) + { + return Icelake::flip(input); + } + + #else + + #endif + + return Scalar::flip(input); + } + + inline auto flip(const input_type_of input) noexcept -> std::basic_string::value_type> + { + [[maybe_unused]] const auto supported = platform::detect_supported_instruction(); + + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + + if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) + { + return Icelake::flip(input); + } + + #else + + #endif + + return Scalar::flip(input); + } + + inline auto flip(const input_type_of::const_pointer input) noexcept -> std::basic_string::value_type> + { + [[maybe_unused]] const auto supported = platform::detect_supported_instruction(); + + #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + + if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) + { + return Icelake::flip(input); + } + + #else + + #endif + + return Scalar::flip(input); + } +} diff --git a/src/chars/chars.ixx b/src/chars/chars.ixx deleted file mode 100644 index 63ec2ff7..00000000 --- a/src/chars/chars.ixx +++ /dev/null @@ -1,463 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.chars; - -import gal.prometheus.error; - -export import :encoding; - -import :scalar; - -#if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED -import :icelake; -#endif - -#else -#include -#include -#include -#include - -#if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED -#include -#endif - -#endif - -namespace gal::prometheus::chars -{ - namespace chars_detail - { - [[nodiscard]] inline auto detect_supported_instruction() noexcept -> std::uint32_t - { - const static auto supported = error::detect_supported_instruction(); - return supported; - } - - constexpr std::uint32_t icelake_required = - static_cast(error::InstructionSet::BMI1) | - static_cast(error::InstructionSet::AVX2) | - static_cast(error::InstructionSet::BMI2) | - static_cast(error::InstructionSet::AVX512BW) | - static_cast(error::InstructionSet::AVX512VL) | - static_cast(error::InstructionSet::AVX512VBMI2) | - static_cast(error::InstructionSet::AVX512VPOPCNTDQ); - - template - struct endian_selector; - - template<> - struct endian_selector - { - constexpr static auto value = std::endian::native; - }; - - template<> - struct endian_selector - { - constexpr static auto value = std::endian::little; - }; - - template<> - struct endian_selector - { - constexpr static auto value = std::endian::big; - }; - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - - [[nodiscard]] inline auto encoding_of(const std::span input) noexcept -> EncodingType - { - [[maybe_unused]] const auto supported = chars_detail::detect_supported_instruction(); - - #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED - if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) - { - return Encoding<"icelake">::encoding_of(input); - } - #else - #endif - - return Encoding<"scalar">::encoding_of(input); - } - - [[nodiscard]] inline auto encoding_of(const std::span input) noexcept -> EncodingType - { - [[maybe_unused]] const auto supported = chars_detail::detect_supported_instruction(); - - #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED - if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) - { - return Encoding<"icelake">::encoding_of(input); - } - #else - #endif - - return Encoding<"scalar">::encoding_of(input); - } - - template - [[nodiscard]] constexpr auto validate(const typename scalar_processor_of_t::input_type input) noexcept -> std::conditional_t - { - [[maybe_unused]] const auto supported = chars_detail::detect_supported_instruction(); - - #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED - if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) - { - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return simd_processor_of_t::template validate::value, ReturnResultType>(input); - } - else - { - return simd_processor_of_t::template validate(input); - } - } - #else - #endif - - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return scalar_processor_of_t::template validate::value, ReturnResultType>(input); - } - else - { - return scalar_processor_of_t::template validate(input); - } - } - - template - [[nodiscard]] constexpr auto validate(const typename scalar_processor_of_t::pointer_type input) noexcept -> std::conditional_t - { - [[maybe_unused]] const auto supported = chars_detail::detect_supported_instruction(); - - #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED - if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) - { - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return simd_processor_of_t::template validate::value, ReturnResultType>(input); - } - else - { - return simd_processor_of_t::template validate(input); - } - } - #else - #endif - - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return scalar_processor_of_t::template validate::value, ReturnResultType>(input); - } - else - { - return scalar_processor_of_t::template validate(input); - } - } - - template - [[nodiscard]] constexpr auto length(const typename scalar_processor_of_t::input_type input) noexcept -> typename scalar_processor_of_t::size_type - { - [[maybe_unused]] const auto supported = chars_detail::detect_supported_instruction(); - - #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED - if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) - { - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return simd_processor_of_t::template length::value>(input); - } - else - { - return simd_processor_of_t::template length(input); - } - } - #else - #endif - - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return scalar_processor_of_t::template length::value>(input); - } - else - { - return scalar_processor_of_t::template length(input); - } - } - - template - [[nodiscard]] constexpr auto length(const typename scalar_processor_of_t::pointer_type input) noexcept -> typename scalar_processor_of_t::size_type - { - [[maybe_unused]] const auto supported = chars_detail::detect_supported_instruction(); - - #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED - if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) - { - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return simd_processor_of_t::template length::value>(input); - } - else - { - return simd_processor_of_t::template length(input); - } - } - #else - #endif - - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return scalar_processor_of_t::template length::value>(input); - } - else - { - return scalar_processor_of_t::template length(input); - } - } - - template - [[nodiscard]] constexpr auto convert( - const typename scalar_processor_of_t::input_type input, - typename output_type::pointer output - ) noexcept -> std::conditional_t - { - [[maybe_unused]] const auto supported = chars_detail::detect_supported_instruction(); - - #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED - if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) - { - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return simd_processor_of_t::template convert::value>(input, output); - } - else - { - return simd_processor_of_t::template convert(input, output); - } - } - #else - #endif - - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return scalar_processor_of_t::template convert::value>(input, output); - } - else - { - return scalar_processor_of_t::template convert(input, output); - } - } - - template - [[nodiscard]] constexpr auto convert( - const typename scalar_processor_of_t::pointer_type input, - typename output_type::pointer output - ) noexcept -> std::conditional_t - { - [[maybe_unused]] const auto supported = chars_detail::detect_supported_instruction(); - - #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED - if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) - { - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return simd_processor_of_t::template convert::value>(input, output); - } - else - { - return simd_processor_of_t::template convert(input, output); - } - } - #else - #endif - - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return scalar_processor_of_t::template convert::value>(input, output); - } - else - { - return scalar_processor_of_t::template convert(input, output); - } - } - - template - [[nodiscard]] constexpr auto convert(const typename scalar_processor_of_t::input_type input) noexcept -> StringType - { - [[maybe_unused]] const auto supported = chars_detail::detect_supported_instruction(); - - #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED - if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) - { - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return simd_processor_of_t::template convert::value>(input); - } - else - { - return simd_processor_of_t::template convert(input); - } - } - #else - #endif - - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return scalar_processor_of_t::template convert::value>(input); - } - else - { - return scalar_processor_of_t::template convert(input); - } - } - - template - [[nodiscard]] constexpr auto convert(const typename scalar_processor_of_t::pointer_type input) noexcept -> StringType - { - [[maybe_unused]] const auto supported = chars_detail::detect_supported_instruction(); - - #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED - if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) - { - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return simd_processor_of_t::template convert::value>(input); - } - else - { - return simd_processor_of_t::template convert(input); - } - } - #else - #endif - - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return scalar_processor_of_t::template convert::value>(input); - } - else - { - return scalar_processor_of_t::template convert(input); - } - } - - template - [[nodiscard]] constexpr auto convert(const typename scalar_processor_of_t::input_type input) noexcept -> std::basic_string::value_type> - { - [[maybe_unused]] const auto supported = chars_detail::detect_supported_instruction(); - - #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED - if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) - { - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return simd_processor_of_t::template convert::value>(input); - } - else - { - return simd_processor_of_t::template convert(input); - } - } - #else - #endif - - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return scalar_processor_of_t::template convert::value>(input); - } - else - { - return scalar_processor_of_t::template convert(input); - } - } - - template - [[nodiscard]] constexpr auto convert(const typename scalar_processor_of_t::pointer_type input) noexcept -> std::basic_string::value_type> - { - [[maybe_unused]] const auto supported = chars_detail::detect_supported_instruction(); - - #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED - if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) - { - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return simd_processor_of_t::template convert::value>(input); - } - else - { - return simd_processor_of_t::template convert(input); - } - } - #else - #endif - - if constexpr (requires { chars_detail::endian_selector::value; }) - { - return scalar_processor_of_t::template convert::value>(input); - } - else - { - return scalar_processor_of_t::template convert(input); - } - } - - inline auto flip_endian(const scalar_processor_of_t::input_type input, const output_type::pointer output) noexcept -> void - { - [[maybe_unused]] const auto supported = chars_detail::detect_supported_instruction(); - - #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED - if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) - { - return simd_processor_of_t::flip_endian(input, output); - } - #else - #endif - - return scalar_processor_of_t::flip_endian(input, output); - } - - template - [[nodiscard]] constexpr auto flip_endian(const scalar_processor_of_t::input_type input) noexcept -> StringType - { - [[maybe_unused]] const auto supported = chars_detail::detect_supported_instruction(); - - #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED - if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) - { - return simd_processor_of_t::flip_endian(input); - } - #else - #endif - - return scalar_processor_of_t::flip_endian(input); - } - - [[nodiscard]] inline auto flip_endian(const scalar_processor_of_t::input_type input) noexcept -> std::basic_string::value_type> - { - [[maybe_unused]] const auto supported = chars_detail::detect_supported_instruction(); - - #if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED - if ((supported & chars_detail::icelake_required) == chars_detail::icelake_required) - { - return simd_processor_of_t::flip_endian(input); - } - #else - #endif - - return scalar_processor_of_t::flip_endian(input); - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -} diff --git a/src/chars/def.hpp b/src/chars/def.hpp new file mode 100644 index 00000000..66289b5b --- /dev/null +++ b/src/chars/def.hpp @@ -0,0 +1,1275 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include + +namespace gal::prometheus::chars +{ + enum class EncodingType : std::uint8_t + { + UNKNOWN = 0b0000'0000, + + // BOM 0xef 0xbb 0xbf + UTF8 = 0b0000'0001, + // BOM 0xff 0xfe + UTF16_LE = 0b0000'0010, + // BOM 0xfe 0xff + UTF16_BE = 0b0000'0100, + // BOM 0xff 0xfe 0x00 0x00 + UTF32_LE = 0b0000'1000, + // BOM 0x00 0x00 0xfe 0xff + UTF32_BE = 0b0001'0000, + }; + + enum class CharsType : std::uint8_t + { + LATIN = 0b0000'0001, + + UTF8_CHAR = 0b0000'0010, + UTF8 = 0b0000'0100, + + UTF16_LE = 0b0000'1000, + UTF16_BE = 0b0001'0000, + // Only for endianness-free functions, e.g. to calculate the length of a string + UTF16 = UTF16_LE | UTF16_BE, + + UTF32 = 0b0010'0000, + }; + + namespace def_detail + { + template + struct io_selector; + + // ====================================== + // LATIN + // ====================================== + + template<> + struct io_selector + { + using input = std::span; + using output = std::span; + constexpr static auto value = CharsType::LATIN; + }; + + // ====================================== + // UTF8_CHAR + // ====================================== + + template<> + struct io_selector + { + using input = std::span; + using output = std::span; + constexpr static auto value = CharsType::UTF8_CHAR; + }; + + // ====================================== + // UTF8 + // ====================================== + + template<> + struct io_selector + { + using input = std::span; + using output = std::span; + constexpr static auto value = CharsType::UTF8; + }; + + // ====================================== + // UTF16_LE + // ====================================== + + template<> + struct io_selector + { + using input = std::span; + using output = std::span; + constexpr static auto value = CharsType::UTF16_LE; + }; + + // ====================================== + // UTF16_LE + // ====================================== + + template<> + struct io_selector + { + using input = std::span; + using output = std::span; + constexpr static auto value = CharsType::UTF16_BE; + }; + + // ====================================== + // UTF16 + // ====================================== + + template<> + struct io_selector + { + using input = std::span; + using output = std::span; + constexpr static auto value = CharsType::UTF16; + }; + + // ====================================== + // UTF32 + // ====================================== + + template<> + struct io_selector + { + using input = std::span; + using output = std::span; + constexpr static auto value = CharsType::UTF32; + }; + + template + struct type_of; + + template + requires requires + { + { T::value } -> std::same_as; + } + struct type_of + { + constexpr static auto value = T::value; + }; + } + + template + constexpr auto chars_type_of = def_detail::type_of::value; + + template + using input_type_of = typename def_detail::io_selector::input; + + template + using output_type_of = typename def_detail::io_selector::output; + + // ReSharper disable CommentTypo + + enum class ErrorCode : std::uint8_t + { + NONE = 0, + + // The decoded character must be not in U+D800...DFFF (UTF-8 or UTF-32) OR + // a high surrogate must be followed by a low surrogate and a low surrogate must be preceded by a high surrogate (UTF-16) OR + // there must be no surrogate at all (Latin1) + SURROGATE, + + // The leading byte must be followed by N-1 continuation bytes, where N is the UTF-8 character length. + // This is also the error when the input is truncated. + TOO_SHORT, + + // We either have too many consecutive continuation bytes or the string starts with a continuation byte. + TOO_LONG, + + // The decoded character must be above U+7F for two-byte characters, U+7FF for three-byte characters, and U+FFFF for four-byte characters. + OVERLONG, + + // The decoded character must be less than or equal to U+10FFFF, less than or equal than U+7F for ASCII OR less than equal than U+FF for Latin1. + TOO_LARGE, + + // Any byte must have fewer than 5 header bits. + HEADER_BITS, + }; + + // ReSharper restore CommentTypo + + struct result_error_input_type + { + ErrorCode error; + std::size_t input; + + [[nodiscard]] constexpr auto has_error() const noexcept -> bool + { + return error != ErrorCode::NONE; + } + + [[nodiscard]] constexpr explicit operator bool() const noexcept + { + return not has_error(); + } + }; + + struct result_error_input_output_type + { + ErrorCode error; + std::size_t input; + std::size_t output; + + [[nodiscard]] constexpr auto has_error() const noexcept -> bool + { + return error != ErrorCode::NONE; + } + + [[nodiscard]] constexpr explicit operator bool() const noexcept + { + return not has_error(); + } + }; + + struct result_output_type + { + std::size_t output; + }; + + [[nodiscard]] auto width_of(EncodingType type) noexcept -> std::size_t; + [[nodiscard]] auto bom_of(std::span string) noexcept -> EncodingType; + [[nodiscard]] auto bom_of(std::span string) noexcept -> EncodingType; + + namespace latin + { + using input_type = input_type_of; + using char_type = input_type::value_type; + using size_type = input_type::size_type; + using pointer_type = input_type::const_pointer; + + /** + * @brief Checks if there is at least one valid `ASCII` code point in the range of [@c current, @c end]. + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto validate(pointer_type current, pointer_type end) noexcept -> std::pair; + + // ======================================================= + // LATIN => UTF8_CHAR + + /** + * @brief If there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf8_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf8_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // LATIN => UTF8 + + /** + * @brief If there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf8_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf8_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // LATIN => UTF16_LE + + /** + * @brief If there is at least one valid `UTF16 (little-endian)` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF16 (little-endian)` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf16_le_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF16 (little-endian)` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf16_le_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // LATIN => UTF16_BE + + /** + * @brief If there is at least one valid `UTF16 (big-endian)` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF16 (big-endian)` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf16_be_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF16 (big-endian)` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf16_be_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // LATIN => UTF32 + + /** + * @brief If there is at least one valid `UTF32` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf32( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF32` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf32_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF32` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf32_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + } + + namespace utf8_char + { + using input_type = input_type_of; + using char_type = input_type::value_type; + using size_type = input_type::size_type; + using pointer_type = input_type::const_pointer; + + /** + * @brief Checks if there is at least one valid `UTF8` code point in the range of [@c current, @c end]. + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto validate(pointer_type current, pointer_type end) noexcept -> std::pair; + + // ======================================================= + // UTF8 => LATIN + + /** + * @brief If there is at least one valid `LATIN` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_latin( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `LATIN` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_latin_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `LATIN` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_latin_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // UTF8 => UTF16_LE + + /** + * @brief If there is at least one valid `UTF16 (little-endian)` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF16 (little-endian)` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf16_le_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF16 (little-endian)` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf16_le_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // UTF8 => UTF16_BE + + /** + * @brief If there is at least one valid `UTF16 (big-endian)` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF16 (big-endian)` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf16_be_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF16 (big-endian)` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf16_be_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // UTF8 => UTF32 + + /** + * @brief If there is at least one valid `UTF32` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf32( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF32` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf32_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF32` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf32_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // UTF8_CHAR => UTF8 + + /** + * @brief If there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf8_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf8_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + } + + namespace utf8 + { + using input_type = input_type_of; + using char_type = input_type::value_type; + using size_type = input_type::size_type; + using pointer_type = input_type::const_pointer; + + /** + * @brief Checks if there is at least one valid `UTF8` code point in the range of [@c current, @c end]. + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto validate(pointer_type current, pointer_type end) noexcept -> std::pair; + + // ======================================================= + // UTF8 => LATIN + + /** + * @brief If there is at least one valid `LATIN` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_latin( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `LATIN` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_latin_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `LATIN` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_latin_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // UTF8 => UTF16_LE + + /** + * @brief If there is at least one valid `UTF16 (little-endian)` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF16 (little-endian)` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf16_le_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF16 (little-endian)` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf16_le_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // UTF8 => UTF16_BE + + /** + * @brief If there is at least one valid `UTF16 (big-endian)` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF16 (big-endian)` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf16_be_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF16 (big-endian)` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf16_be_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // UTF8 => UTF32 + + /** + * @brief If there is at least one valid `UTF32` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf32( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF32` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf32_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF32` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf32_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // UTF8_CHAR => UTF8 + + /** + * @brief If there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf8_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf8_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + } + + namespace utf16 + { + using input_type = input_type_of; + static_assert(std::is_same_v>); + static_assert(std::is_same_v>); + + using char_type = input_type::value_type; + using size_type = input_type::size_type; + using pointer_type = input_type::const_pointer; + + /** + * @brief Checks if there is at least one valid `UTF16 (little-endian)` code point in the range of [@c current, @c end]. + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto validate_le(pointer_type current, pointer_type end) noexcept -> std::pair; + + /** + * @brief Checks if there is at least one valid `UTF16 (big-endian)` code point in the range of [@c current, @c end]. + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto validate_be(pointer_type current, pointer_type end) noexcept -> std::pair; + + // ======================================================= + // UTF16 => LATIN + + /** + * @brief If there is at least one valid `LATIN` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_latin_le( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief If there is at least one valid `LATIN` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_latin_be( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `LATIN` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_latin_pure_le( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `LATIN` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_latin_pure_be( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `LATIN` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_latin_correct_le( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `LATIN` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_latin_correct_be( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // UTF16 => UTF8_CHAR + + /** + * @brief If there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf8_le( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief If there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf8_be( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf8_pure_le( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf8_pure_be( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf8_correct_le( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf8_correct_be( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // UTF16 => UTF8 + + /** + * @brief If there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf8_le( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief If there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf8_be( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf8_pure_le( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf8_pure_be( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf8_correct_le( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf8_correct_be( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // UTF16 => UTF32 + + /** + * @brief If there is at least one valid `UTF32` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf32_le( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief If there is at least one valid `UTF32` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf32_be( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF32` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf32_pure_le( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF32` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf32_pure_be( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF32` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf32_correct_le( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF32` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf32_correct_be( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + } + + namespace utf32 + { + using input_type = input_type_of; + using char_type = input_type::value_type; + using size_type = input_type::size_type; + using pointer_type = input_type::const_pointer; + + /** + * @brief Checks if there is at least one valid `UTF32` code point in the range of [@c current, @c end]. + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto validate(pointer_type current, pointer_type end) noexcept -> std::pair; + + // ======================================================= + // UTF32 => LATIN + + /** + * @brief If there is at least one valid `LATIN` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_latin( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `LATIN` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_latin_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `LATIN` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_latin_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // UTF32 => UTF8_CHAR + + /** + * @brief If there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf8_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf8_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // UTF32 => UTF8 + + /** + * @brief If there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf8_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF8` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf8_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // UTF32 => UTF16_LE + + /** + * @brief If there is at least one valid `UTF16 (little-endian)` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF16 (little-endian)` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf16_le_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF16 (little-endian)` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf16_le_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + // ======================================================= + // UTF32 => UTF16_BE + + /** + * @brief If there is at least one valid `UTF16 (big-endian)` code point in the range of [@c current, @c end], + * write that code point to @c output and iterate the @c output pointer (according to the number of code points actually written). + * @return {(how many iterations of the input pointer are required for the code point processed), (is the code point valid)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF16 (big-endian)` code point in the range of [@c current, @c end], + * and it is ASCII. + * @return You can assume that this function always returns {1, @c ErrorCode::NONE} + */ + [[nodiscard]] auto write_utf16_be_pure( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + + /** + * @brief Assume that there is at least one valid `UTF16 (big-endian)` code point in the range of [@c current, @c end]. + * @return You can assume that this function always returns {N, @c ErrorCode::NONE}, the value of N depends on the code point. + */ + [[nodiscard]] auto write_utf16_be_correct( + output_type_of::pointer& output, + pointer_type current, + pointer_type end + ) noexcept -> std::pair; + } +} diff --git a/src/chars/detail/icelake.utf32.hpp b/src/chars/detail/icelake.utf32.hpp new file mode 100644 index 00000000..162b620f --- /dev/null +++ b/src/chars/detail/icelake.utf32.hpp @@ -0,0 +1,542 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#if defined(GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED) + +namespace detail::utf32::icelake +{ + struct utf16_to_utf8 + { + // 1 byte for length, 16 bytes for mask + constexpr static std::array, 256> _1_2 + { + { + {16, 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14}, + {15, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80}, + {15, 1, 0, 3, 2, 5, 4, 7, 6, 8, 11, 10, 13, 12, 15, 14, 0x80}, + {14, 0, 3, 2, 5, 4, 7, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, + {15, 1, 0, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80}, + {14, 0, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, + {14, 1, 0, 2, 5, 4, 7, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, + {13, 0, 2, 5, 4, 7, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {15, 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 10, 13, 12, 15, 14, 0x80}, + {14, 0, 3, 2, 5, 4, 7, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80}, + {14, 1, 0, 3, 2, 5, 4, 7, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80}, + {13, 0, 3, 2, 5, 4, 7, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {14, 1, 0, 2, 5, 4, 7, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80}, + {13, 0, 2, 5, 4, 7, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {13, 1, 0, 2, 5, 4, 7, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 2, 5, 4, 7, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {15, 1, 0, 3, 2, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80}, + {14, 0, 3, 2, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, + {14, 1, 0, 3, 2, 4, 7, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, + {13, 0, 3, 2, 4, 7, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {14, 1, 0, 2, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, + {13, 0, 2, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {13, 1, 0, 2, 4, 7, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 2, 4, 7, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {14, 1, 0, 3, 2, 4, 7, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80}, + {13, 0, 3, 2, 4, 7, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 4, 7, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 4, 7, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 2, 4, 7, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 2, 4, 7, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 4, 7, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 4, 7, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {15, 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 12, 15, 14, 0x80}, + {14, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80}, + {14, 1, 0, 3, 2, 5, 4, 7, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80}, + {13, 0, 3, 2, 5, 4, 7, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, + {14, 1, 0, 2, 5, 4, 7, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80}, + {13, 0, 2, 5, 4, 7, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, + {13, 1, 0, 2, 5, 4, 7, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 2, 5, 4, 7, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {14, 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80}, + {13, 0, 3, 2, 5, 4, 7, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 5, 4, 7, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 5, 4, 7, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 2, 5, 4, 7, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 2, 5, 4, 7, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 5, 4, 7, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 5, 4, 7, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {14, 1, 0, 3, 2, 4, 7, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80}, + {13, 0, 3, 2, 4, 7, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 4, 7, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 4, 7, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 2, 4, 7, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 2, 4, 7, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 4, 7, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 4, 7, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 4, 7, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 4, 7, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 3, 2, 4, 7, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 3, 2, 4, 7, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 4, 7, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 4, 7, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 2, 4, 7, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 2, 4, 7, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {15, 1, 0, 3, 2, 5, 4, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80}, + {14, 0, 3, 2, 5, 4, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, + {14, 1, 0, 3, 2, 5, 4, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, + {13, 0, 3, 2, 5, 4, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {14, 1, 0, 2, 5, 4, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, + {13, 0, 2, 5, 4, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {13, 1, 0, 2, 5, 4, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 2, 5, 4, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {14, 1, 0, 3, 2, 5, 4, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80}, + {13, 0, 3, 2, 5, 4, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 5, 4, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 5, 4, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 2, 5, 4, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 2, 5, 4, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 5, 4, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 5, 4, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {14, 1, 0, 3, 2, 4, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, + {13, 0, 3, 2, 4, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 4, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 4, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 2, 4, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 2, 4, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 4, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 4, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 4, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 4, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 3, 2, 4, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 3, 2, 4, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 4, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 4, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 2, 4, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 2, 4, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {14, 1, 0, 3, 2, 5, 4, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80}, + {13, 0, 3, 2, 5, 4, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 5, 4, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 5, 4, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 2, 5, 4, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 2, 5, 4, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 5, 4, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 5, 4, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 5, 4, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 5, 4, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 3, 2, 5, 4, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 3, 2, 5, 4, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 5, 4, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 5, 4, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 2, 5, 4, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 2, 5, 4, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 4, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 4, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 3, 2, 4, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 3, 2, 4, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 4, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 4, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 2, 4, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 2, 4, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 3, 2, 4, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 3, 2, 4, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 3, 2, 4, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 3, 2, 4, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 2, 4, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 2, 4, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 1, 0, 2, 4, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 0, 2, 4, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {15, 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 14, 0x80}, + {14, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80}, + {14, 1, 0, 3, 2, 5, 4, 7, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80}, + {13, 0, 3, 2, 5, 4, 7, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, + {14, 1, 0, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80}, + {13, 0, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, + {13, 1, 0, 2, 5, 4, 7, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, + {12, 0, 2, 5, 4, 7, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {14, 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80}, + {13, 0, 3, 2, 5, 4, 7, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 5, 4, 7, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 5, 4, 7, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 2, 5, 4, 7, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80}, + {12, 0, 2, 5, 4, 7, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 5, 4, 7, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 5, 4, 7, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {14, 1, 0, 3, 2, 4, 7, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80}, + {13, 0, 3, 2, 4, 7, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 4, 7, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 4, 7, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 2, 4, 7, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, + {12, 0, 2, 4, 7, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 4, 7, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 4, 7, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 4, 7, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 4, 7, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 3, 2, 4, 7, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 3, 2, 4, 7, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 4, 7, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 4, 7, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 2, 4, 7, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 2, 4, 7, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {14, 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80}, + {13, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 5, 4, 7, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 5, 4, 7, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 2, 5, 4, 7, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80}, + {12, 0, 2, 5, 4, 7, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 5, 4, 7, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 5, 4, 7, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 5, 4, 7, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 3, 2, 5, 4, 7, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 3, 2, 5, 4, 7, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 5, 4, 7, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 5, 4, 7, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 2, 5, 4, 7, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 2, 5, 4, 7, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 4, 7, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 4, 7, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 3, 2, 4, 7, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 3, 2, 4, 7, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 4, 7, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 4, 7, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 2, 4, 7, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 2, 4, 7, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 3, 2, 4, 7, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 3, 2, 4, 7, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 3, 2, 4, 7, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 3, 2, 4, 7, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 2, 4, 7, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 2, 4, 7, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 1, 0, 2, 4, 7, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 0, 2, 4, 7, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {14, 1, 0, 3, 2, 5, 4, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80}, + {13, 0, 3, 2, 5, 4, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 5, 4, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 5, 4, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 2, 5, 4, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, + {12, 0, 2, 5, 4, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 5, 4, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 5, 4, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 5, 4, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 5, 4, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 3, 2, 5, 4, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 3, 2, 5, 4, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 5, 4, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 5, 4, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 2, 5, 4, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 2, 5, 4, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 4, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 4, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 3, 2, 4, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 3, 2, 4, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 4, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 4, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 2, 4, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 2, 4, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 3, 2, 4, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 3, 2, 4, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 3, 2, 4, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 3, 2, 4, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 2, 4, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 2, 4, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 1, 0, 2, 4, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 0, 2, 4, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {13, 1, 0, 3, 2, 5, 4, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80}, + {12, 0, 3, 2, 5, 4, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 3, 2, 5, 4, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 3, 2, 5, 4, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 2, 5, 4, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 2, 5, 4, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 2, 5, 4, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 2, 5, 4, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 3, 2, 5, 4, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 3, 2, 5, 4, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 3, 2, 5, 4, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 3, 2, 5, 4, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 2, 5, 4, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 2, 5, 4, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 1, 0, 2, 5, 4, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 0, 2, 5, 4, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {12, 1, 0, 3, 2, 4, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, + {11, 0, 3, 2, 4, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 3, 2, 4, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 3, 2, 4, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 2, 4, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 2, 4, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 1, 0, 2, 4, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 0, 2, 4, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 1, 0, 3, 2, 4, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 3, 2, 4, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 1, 0, 3, 2, 4, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 0, 3, 2, 4, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 1, 0, 2, 4, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 0, 2, 4, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 1, 0, 2, 4, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 0, 2, 4, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80} + } + }; + + // 1 byte for length, 16 bytes for mask + constexpr static std::array, 256> _1_2_3 + { + { + {12, 2, 3, 1, 6, 7, 5, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80}, + {9, 6, 7, 5, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 3, 1, 6, 7, 5, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 0, 6, 7, 5, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 2, 3, 1, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 3, 1, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 0, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 2, 3, 1, 7, 5, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 7, 5, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 3, 1, 7, 5, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 0, 7, 5, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 2, 3, 1, 4, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 4, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 3, 1, 4, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 0, 4, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 2, 3, 1, 6, 7, 5, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 6, 7, 5, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 3, 1, 6, 7, 5, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 0, 6, 7, 5, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 2, 3, 1, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 3, 1, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 0, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 2, 3, 1, 7, 5, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 7, 5, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 3, 1, 7, 5, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 0, 7, 5, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 2, 3, 1, 4, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 4, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 3, 1, 4, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 0, 4, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 2, 3, 1, 6, 7, 5, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 6, 7, 5, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 3, 1, 6, 7, 5, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 0, 6, 7, 5, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 2, 3, 1, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 3, 1, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 0, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 2, 3, 1, 7, 5, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 7, 5, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 3, 1, 7, 5, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 0, 7, 5, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 2, 3, 1, 4, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 4, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 3, 1, 4, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 0, 4, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 2, 3, 1, 6, 7, 5, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 6, 7, 5, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 3, 1, 6, 7, 5, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 0, 6, 7, 5, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 2, 3, 1, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 3, 1, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 0, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 2, 3, 1, 7, 5, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 7, 5, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 3, 1, 7, 5, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 0, 7, 5, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 2, 3, 1, 4, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 4, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 3, 1, 4, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 0, 4, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 2, 3, 1, 6, 7, 5, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 6, 7, 5, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 3, 1, 6, 7, 5, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 0, 6, 7, 5, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 2, 3, 1, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 3, 1, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 0, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 2, 3, 1, 7, 5, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 7, 5, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 3, 1, 7, 5, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 0, 7, 5, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 2, 3, 1, 4, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 4, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 3, 1, 4, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 0, 4, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 2, 3, 1, 6, 7, 5, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 6, 7, 5, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 3, 1, 6, 7, 5, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 0, 6, 7, 5, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 2, 3, 1, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {2, 3, 1, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {1, 0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 2, 3, 1, 7, 5, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {2, 7, 5, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 3, 1, 7, 5, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 0, 7, 5, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 2, 3, 1, 4, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {1, 4, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 3, 1, 4, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {2, 0, 4, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 2, 3, 1, 6, 7, 5, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 6, 7, 5, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 3, 1, 6, 7, 5, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 0, 6, 7, 5, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 2, 3, 1, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {2, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 3, 1, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 0, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 2, 3, 1, 7, 5, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 7, 5, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 3, 1, 7, 5, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 0, 7, 5, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 2, 3, 1, 4, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 4, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 3, 1, 4, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 0, 4, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 2, 3, 1, 6, 7, 5, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 6, 7, 5, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 3, 1, 6, 7, 5, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 0, 6, 7, 5, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 2, 3, 1, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {1, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 3, 1, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {2, 0, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 2, 3, 1, 7, 5, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 7, 5, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 3, 1, 7, 5, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 0, 7, 5, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 2, 3, 1, 4, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {2, 4, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 3, 1, 4, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 0, 4, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {11, 2, 3, 1, 6, 7, 5, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 6, 7, 5, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 3, 1, 6, 7, 5, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 0, 6, 7, 5, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 2, 3, 1, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 3, 1, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 0, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 2, 3, 1, 7, 5, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 7, 5, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 3, 1, 7, 5, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 0, 7, 5, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 2, 3, 1, 4, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 4, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 3, 1, 4, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 0, 4, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 2, 3, 1, 6, 7, 5, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 6, 7, 5, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 3, 1, 6, 7, 5, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 0, 6, 7, 5, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 2, 3, 1, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {2, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 3, 1, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 0, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 2, 3, 1, 7, 5, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 7, 5, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 3, 1, 7, 5, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 0, 7, 5, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 2, 3, 1, 4, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 4, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 3, 1, 4, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 0, 4, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 2, 3, 1, 6, 7, 5, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 6, 7, 5, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 3, 1, 6, 7, 5, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 0, 6, 7, 5, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 2, 3, 1, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 3, 1, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 0, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 2, 3, 1, 7, 5, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 7, 5, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 3, 1, 7, 5, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 0, 7, 5, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 2, 3, 1, 4, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 4, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 3, 1, 4, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 0, 4, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 2, 3, 1, 6, 7, 5, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 6, 7, 5, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 3, 1, 6, 7, 5, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 0, 6, 7, 5, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 2, 3, 1, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 3, 1, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 0, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 2, 3, 1, 7, 5, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 7, 5, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 3, 1, 7, 5, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 0, 7, 5, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 2, 3, 1, 4, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 4, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 3, 1, 4, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 0, 4, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {10, 2, 3, 1, 6, 7, 5, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 6, 7, 5, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 3, 1, 6, 7, 5, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 0, 6, 7, 5, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 2, 3, 1, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 3, 1, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 0, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 2, 3, 1, 7, 5, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 7, 5, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 3, 1, 7, 5, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 0, 7, 5, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 2, 3, 1, 4, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 4, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 3, 1, 4, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 0, 4, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 2, 3, 1, 6, 7, 5, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 6, 7, 5, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 3, 1, 6, 7, 5, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 0, 6, 7, 5, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 2, 3, 1, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {1, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 3, 1, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {2, 0, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 2, 3, 1, 7, 5, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 7, 5, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 3, 1, 7, 5, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 0, 7, 5, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 2, 3, 1, 4, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {2, 4, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 3, 1, 4, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 0, 4, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {9, 2, 3, 1, 6, 7, 5, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 6, 7, 5, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 3, 1, 6, 7, 5, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 0, 6, 7, 5, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 2, 3, 1, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 3, 1, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 0, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 2, 3, 1, 7, 5, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 7, 5, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 3, 1, 7, 5, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 0, 7, 5, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 2, 3, 1, 4, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 4, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 3, 1, 4, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 0, 4, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {8, 2, 3, 1, 6, 7, 5, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 6, 7, 5, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 3, 1, 6, 7, 5, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 0, 6, 7, 5, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 2, 3, 1, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {2, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 3, 1, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 0, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {7, 2, 3, 1, 7, 5, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 7, 5, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 3, 1, 7, 5, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 0, 7, 5, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {6, 2, 3, 1, 4, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {3, 4, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {5, 3, 1, 4, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, + {4, 0, 4, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80} + } + }; + }; +} + +#endif diff --git a/src/chars/detail/icelake.utf8.hpp b/src/chars/detail/icelake.utf8.hpp new file mode 100644 index 00000000..10a88ac2 --- /dev/null +++ b/src/chars/detail/icelake.utf8.hpp @@ -0,0 +1,524 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#if defined(GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED) + +namespace detail::utf8::icelake +{ + template + requires + (I0 <= 3) and + (I1 <= 3) and + (I2 <= 3) and + (I3 <= 3) + [[nodiscard]] auto shuffle(const data_type value) noexcept -> data_type + { + constexpr auto s = static_cast(I0 | (I1 << 2) | (I2 << 4) | (I3 << 6)); + return _mm512_shuffle_i32x4(value, value, s); + } + + template + requires(I <= 3) + [[nodiscard]] auto broadcast(const data_type value) noexcept -> data_type // + { + return shuffle(value); + } + + [[nodiscard]] inline auto expand_and_identify(const data_type lane_0, const data_type lane_1, std::uint8_t& valid_count) noexcept -> data_type + { + const auto expand_ver2 = _mm512_setr_epi64( + 0x0403'0201'0302'0100, + 0x0605'0403'0504'0302, + 0x0807'0605'0706'0504, + 0x0a09'0807'0908'0706, + 0x0c0b'0a09'0b0a'0908, + 0x0e0d'0c0b'0d0c'0b0a, + 0x000f'0e0d'0f0e'0d0c, + 0x0201'000f'0100'0f0e + ); + + const auto v_00c0 = _mm512_set1_epi32(0x00c0); + const auto v_0080 = _mm512_set1_epi32(0x0080); + + const auto merged = _mm512_mask_mov_epi32(lane_0, 0x1000, lane_1); + const auto input = _mm512_shuffle_epi8(merged, expand_ver2); + const auto t0 = _mm512_and_si512(input, v_00c0); + const auto leading_bytes = _mm512_cmpneq_epu32_mask(t0, v_0080); + + valid_count = static_cast(std::popcount(leading_bytes)); + return _mm512_mask_compress_epi32(_mm512_setzero_si512(), leading_bytes, input); + } + + [[nodiscard]] inline auto expand_to_utf32(const data_type data, const data_type char_class) noexcept -> data_type + { + // Input: + // - utf8: bytes stored at separate 32-bit code units + // - valid: which code units have valid UTF-8 characters + // + // Bit layout of single word. + // We show 4 cases for each possible UTF-8 character encoding. + // The `?` denotes bits we must not assume their value. + // + // |10dd.dddd|10cc.cccc|10bb.bbbb|1111.0aaa| 4-byte char + // |????.???? |10cc.cccc|10bb.bbbb|1110.aaaa| 3-byte char + // |????.???? |????.???? |10bb.bbbb|110a.aaaa| 2-byte char + // |????.???? |????.???? |????.???? |0aaa.aaaa| ASCII char + // byte 3 byte 2 byte 1 byte 0 + + const auto v_3f3f_3f7f = _mm512_set1_epi32(0x3f3f'3f7f); + const auto v_0140_0140 = _mm512_set1_epi32(0x0140'0140); + const auto v_0001_1000 = _mm512_set1_epi32(0x0001'1000); + + // ReSharper disable once CppJoinDeclarationAndAssignment + data_type result; + + // Reset control bits of continuation bytes and the MSB of the leading byte, + // this makes all bytes unsigned (and does not alter ASCII char). + // + // |00dd.dddd|00cc.cccc|00bb.bbbb|0111.0aaa| 4-byte char + // |00??.???? |00cc.cccc|00bb.bbbb|0110.aaaa| 3-byte char + // |00??.???? |00??.???? |00bb.bbbb|010a.aaaa| 2-byte char + // |00??.???? |00??.???? |00??.???? |0aaa.aaaa| ASCII char + result = _mm512_and_si512(data, v_3f3f_3f7f); + + // Swap and join fields A-B and C-D + // + // |0000.cccc|ccdd.dddd|0001.110a|aabb.bbbb| 4-byte char + // |0000.cccc|cc??.???? |0001.10aa|aabb.bbbb| 3-byte char + // |0000.????|????.???? |0001.0aaa|aabb.bbbb| 2-byte char + // |0000.????|????.???? |000a.aaaa|aa??.???? | ASCII char + result = _mm512_maddubs_epi16(result, v_0140_0140); + + // Swap and join fields AB & CD + // + // |0000.0001|110a.aabb|bbbb.cccc|ccdd.dddd| 4-byte char + // |0000.0001|10aa.aabb|bbbb.cccc|cc??.???? | 3-byte char + // |0000.0001|0aaa.aabb|bbbb.????|????.???? | 2-byte char + // |0000.000a|aaaa.aa?? |????.???? |????.???? | ASCII char + result = _mm512_madd_epi16(result, v_0001_1000); + + // Shift left the values by variable amounts to reset highest UTF-8 bits + // + // |aaab.bbbb|bccc.cccd|dddd.d000|0000.0000| 4-byte char -- by 11 + // |aaaa.bbbb|bbcc.cccc|????.??00 |0000.0000| 3-byte char -- by 10 + // |aaaa.abbb|bbb?.????|????.???0 |0000.0000| 2-byte char -- by 9 + // |aaaa.aaa? |????.???? |????.???? |?000.0000| ASCII char -- by 7 + { + // continuation = 0 + // ascii = 7 + // 2_bytes = 9 + // 3_bytes = 10 + // 4_bytes = 11 + // + // shift_left_v3 = 4 * [ + // ascii, # 0000 + // ascii, # 0001 + // ascii, # 0010 + // ascii, # 0011 + // ascii, # 0100 + // ascii, # 0101 + // ascii, # 0110 + // ascii, # 0111 + // continuation, # 1000 + // continuation, # 1001 + // continuation, # 1010 + // continuation, # 1011 + // 2_bytes, # 1100 + // 2_bytes, # 1101 + // 3_bytes, # 1110 + // 4_bytes, # 1111 + // ] + const auto shift_left_v3 = _mm512_setr_epi64( + // clang-format off + 0x0707'0707'0707'0707, 0x0b0a'0909'0000'0000, + 0x0707'0707'0707'0707, 0x0b0a'0909'0000'0000, + 0x0707'0707'0707'0707, 0x0b0a'0909'0000'0000, + 0x0707'0707'0707'0707, 0x0b0a'0909'0000'0000 + // clang-format on + ); + const auto shift = _mm512_shuffle_epi8(shift_left_v3, char_class); + result = _mm512_sllv_epi32(result, shift); + } + + // Shift right the values by variable amounts to reset lowest bits + // + // |0000.0000|000a.aabb|bbbb.cccc |ccdd.dddd| 4-byte char -- by 11 + // |0000.0000|0000.0000|aaaa.bbbb |bbcc.cccc | 3-byte char -- by 16 + // |0000.0000|0000.0000|0000.0aaa |aabb.bbbb| 2-byte char -- by 21 + // |0000.0000|0000.0000|0000.0000 |0aaa.aaaa | ASCII char -- by 25 + { + // 4 * [25, 25, 25, 25, 25, 25, 25, 25, 0, 0, 0, 0, 21, 21, 16, 11] + const auto shift_right = _mm512_setr_epi64( + // clang-format off + 0x1919'1919'1919'1919, 0x0b10'1515'0000'0000, + 0x1919'1919'1919'1919, 0x0b10'1515'0000'0000, + 0x1919'1919'1919'1919, 0x0b10'1515'0000'0000, + 0x1919'1919'1919'1919, 0x0b10'1515'0000'0000 + // clang-format on + ); + const auto shift = _mm512_shuffle_epi8(shift_right, char_class); + result = _mm512_srlv_epi32(result, shift); + } + + return result; + } + + [[nodiscard]] inline auto expand_to_utf32(const data_type data) noexcept -> data_type + { + const auto v_0000_000f = _mm512_set1_epi32(0x0000'000f); + const auto v_8080_8000 = _mm512_set1_epi32(static_cast(0x8080'8000)); + + const auto char_class = _mm512_ternarylogic_epi32(_mm512_srli_epi32(data, 4), v_0000_000f, v_8080_8000, 0xea); + return expand_to_utf32(data, char_class); + } + + template + auto write_utf16_pure(output_type_of::pointer& output, const data_type data, const data_type byte_flip) noexcept -> void + { + constexpr auto iterations = std::ptrdiff_t{32}; + static_assert(sizeof(data_type) / sizeof(output_type_of::value_type) == iterations); + + const auto h0 = _mm512_castsi512_si256(data); + const auto h1 = _mm512_extracti64x4_epi64(data, 1); + + const auto o0 = _mm512_cvtepu8_epi16(h0); + const auto o1 = _mm512_cvtepu8_epi16(h1); + + if constexpr (common::not_native_endian()) + { + _mm512_storeu_si512(output + 0 * iterations, _mm512_shuffle_epi8(o0, byte_flip)); + _mm512_storeu_si512(output + 2 * iterations, _mm512_shuffle_epi8(o1, byte_flip)); + } + else + { + _mm512_storeu_si512(output + 0 * iterations, o0); + _mm512_storeu_si512(output + 2 * iterations, o1); + } + + output += 2 * iterations; + } + + inline auto write_utf32_pure(output_type_of::pointer& output, const data_type data) noexcept -> void + { + // utf8::icelake::advance_of_utf32 + constexpr auto iterations = std::ptrdiff_t{16}; + static_assert(sizeof(data_type) / sizeof(output_type_of::value_type) == iterations); + + const auto t0 = _mm512_castsi512_si128(data); + const auto t1 = _mm512_extracti32x4_epi32(data, 1); + const auto t2 = _mm512_extracti32x4_epi32(data, 2); + const auto t3 = _mm512_extracti32x4_epi32(data, 3); + + _mm512_storeu_si512(output + 0 * iterations, _mm512_cvtepu8_epi32(t0)); + _mm512_storeu_si512(output + 1 * iterations, _mm512_cvtepu8_epi32(t1)); + _mm512_storeu_si512(output + 2 * iterations, _mm512_cvtepu8_epi32(t2)); + _mm512_storeu_si512(output + 3 * iterations, _mm512_cvtepu8_epi32(t3)); + + output += 4 * iterations; + } + + template + auto write_utf16_from_utf32(output_type_of::pointer& output, const data_type data, const std::size_t length, const data_type byte_flip) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length > 0); + + const auto v_0000_ffff = _mm512_set1_epi32(0x0000'ffff); + const auto v_0001_0000 = _mm512_set1_epi32(0x0001'0000); + const auto v_ffff_0000 = _mm512_set1_epi32(static_cast(0xffff'0000)); + const auto v_fc00_fc00 = _mm512_set1_epi32(static_cast(0xfc00'fc00)); + const auto v_d800_dc00 = _mm512_set1_epi32(static_cast(0xd800'dc00)); + + const auto length_mask = static_cast<__mmask16>(_bzhi_u32(~static_cast(0), static_cast(length))); + + const auto surrogate_pair_mask = _mm512_mask_cmpgt_epu32_mask(length_mask, data, v_0000_ffff); + + // check if we have any surrogate pairs + if (surrogate_pair_mask) + { + const auto out = _mm512_cvtepi32_epi16(data); + + if constexpr (common::not_native_endian()) + { + _mm256_mask_storeu_epi16(output, length_mask, _mm256_shuffle_epi8(out, _mm512_castsi512_si256(byte_flip))); + } + else + { + _mm256_mask_storeu_epi16(output, length_mask, out); + } + + output += length; + } + + const auto length_total = length + std::popcount(surrogate_pair_mask); + const auto length_total_mask = _bzhi_u32(~static_cast(0), static_cast(length_total)); + + // build surrogate pair code units in 32-bit lanes + + // t0 = 8 x [000000000000aaaa|aaaaaabbbbbbbbbb] + const auto t0 = _mm512_sub_epi32(data, v_0001_0000); + // t1 = 8 x [000000aaaaaaaaaa|bbbbbbbbbb000000] + const auto t1 = _mm512_slli_epi32(t0, 6); + // t2 = 8 x [000000aaaaaaaaaa|aaaaaabbbbbbbbbb] -- copy hi word from t1 to t0 + // 0xe4 = (t1 and v_ffff_0000) or (t0 and not v_ffff_0000) + const auto t2 = _mm512_ternarylogic_epi32(t1, t0, v_ffff_0000, 0xe4); + // t2 = 8 x [110110aaaaaaaaaa|110111bbbbbbbbbb] -- copy hi word from t1 to t0 + // 0xba = (t2 and not v_fc00_fc000) or v_d800_dc00 + const auto t3 = _mm512_ternarylogic_epi32(t2, v_fc00_fc00, v_d800_dc00, 0xba); + const auto t4 = _mm512_mask_blend_epi32(surrogate_pair_mask, data, t3); + const auto t5 = [t4, byte_flip]() noexcept + { + if constexpr (const auto out = _mm512_ror_epi32(t4, 16); + common::not_native_endian()) + { + return _mm512_shuffle_epi8(out, byte_flip); + } + else + { + std::ignore = byte_flip; + return out; + } + }(); + + const auto non_zero = _kor_mask32(0xaaaa'aaaa, _mm512_cmpneq_epi16_mask(t5, _mm512_setzero_si512())); + // fixme + // _mm512_mask_compressstoreu_epi16(output, non_zero, t5); + _mm512_mask_storeu_epi16( + output, + length_total_mask, + _mm512_maskz_compress_epi16(non_zero, t5) + ); + + output += length_total; + } + + inline auto write_utf32(output_type_of::pointer& output, const data_type data, const std::size_t length) noexcept -> void + { + const auto mask = static_cast<__mmask16>(_bzhi_u32(~static_cast(0), static_cast(length))); + + _mm512_mask_storeu_epi32(output, mask, data); + output += length; + } + + inline auto transcode_16( + output_type_of::pointer& output, + const data_type lane_2, + const data_type lane_3 + ) noexcept -> void + { + // # lane{0,1,2} have got bytes: + // [ 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15] + // # lane3 has got bytes: + // [ 16, 17, 18, 19, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15] + // + // expand_ver2 = [ + // # lane 0: + // 0, 1, 2, 3, + // 1, 2, 3, 4, + // 2, 3, 4, 5, + // 3, 4, 5, 6, + // + // # lane 1: + // 4, 5, 6, 7, + // 5, 6, 7, 8, + // 6, 7, 8, 9, + // 7, 8, 9, 10, + // + // # lane 2: + // 8, 9, 10, 11, + // 9, 10, 11, 12, + // 10, 11, 12, 13, + // 11, 12, 13, 14, + // + // # lane 3 order: 13, 14, 15, 16 14, 15, 16, 17, 15, 16, 17, 18, 16, 17, 18, 19 + // 12, 13, 14, 15, + // 13, 14, 15, 0, + // 14, 15, 0, 1, + // 15, 0, 1, 2, + // ] + const auto expand_ver2 = _mm512_setr_epi64( + 0x0403'0201'0302'0100, + 0x0605'0403'0504'0302, + 0x0807'0605'0706'0504, + 0x0a09'0807'0908'0706, + 0x0c0b'0a09'0b0a'0908, + 0x0e0d'0c0b'0d0c'0b0a, + 0x000f'0e0d'0f0e'0d0c, + 0x0201'000f'0100'0f0e + ); + const auto v_0000_00c0 = _mm512_set1_epi32(0x0000'00c0); + const auto v_0000_0080 = _mm512_set1_epi32(0x0000'0080); + + const auto merged = _mm512_mask_mov_epi32(lane_2, 0x1000, lane_3); + const auto data = _mm512_shuffle_epi8(merged, expand_ver2); + + const auto t0 = _mm512_and_si512(data, v_0000_00c0); + const auto leading_bytes = _mm512_cmpneq_epu32_mask(t0, v_0000_0080); + const auto utf32 = expand_to_utf32(data); + const auto out = _mm512_mask_compress_epi32(_mm512_setzero_si512(), leading_bytes, utf32); + + const auto valid_count = std::popcount(leading_bytes); + + const auto mask = static_cast<__mmask16>(_bzhi_u32(~static_cast(0), static_cast(valid_count))); + _mm512_mask_storeu_epi32(output, mask, out); + output += valid_count; + } + + struct avx512_utf8_checker + { + private: + template + requires(N <= 32) + static auto prev(const data_type input, const data_type prev_input) noexcept -> data_type + { + const auto move_mask = _mm512_setr_epi32(28, 29, 30, 31, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + const auto rotated = _mm512_permutex2var_epi32(input, move_mask, prev_input); + + return _mm512_alignr_epi8(input, rotated, 16 - N); + } + + public: + // If this is nonzero, there has been a UTF-8 error + data_type error{}; + // The last input we received + data_type prev_data_block{}; + // Whether the last input we received was incomplete (used for ASCII fast path) + data_type prev_incomplete{}; + + private: + // Check whether the current bytes are valid UTF-8. + auto check_utf8_bytes(const data_type data, const data_type prev_data) noexcept -> void + { + // Flip prev1...prev3, so we can easily determine if they are 2+, 3+ or 4+ lead bytes + // (2, 3, 4-byte leads become large positive numbers instead of small negative numbers) + const auto prev_1 = prev<1>(data, prev_data); + + // special cases + const auto source = [data, prev_1]() noexcept -> data_type + { + const auto mask1 = _mm512_setr_epi64( + 0x0202'0202'0202'0202, + 0x4915'0121'8080'8080, + 0x0202'0202'0202'0202, + 0x4915'0121'8080'8080, + 0x0202'0202'0202'0202, + 0x4915'0121'8080'8080, + 0x0202'0202'0202'0202, + 0x4915'0121'8080'8080 + ); + const auto mask2 = _mm512_setr_epi64( + static_cast(0xcbcb'cb8b'8383'a3e7), + static_cast(0xcbcb'dbcb'cbcb'cbcb), + static_cast(0xcbcb'cb8b'8383'a3e7), + static_cast(0xcbcb'dbcb'cbcb'cbcb), + static_cast(0xcbcb'cb8b'8383'a3e7), + static_cast(0xcbcb'dbcb'cbcb'cbcb), + static_cast(0xcbcb'cb8b'8383'a3e7), + static_cast(0xcbcb'dbcb'cbcb'cbcb) + ); + const auto mask3 = _mm512_setr_epi64( + 0x0101'0101'0101'0101, + 0x0101'0101'baba'aee6, + 0x0101'0101'0101'0101, + 0x0101'0101'baba'aee6, + 0x0101'0101'0101'0101, + 0x0101'0101'baba'aee6, + 0x0101'0101'0101'0101, + 0x0101'0101'baba'aee6 + ); + + const auto v_0f = _mm512_set1_epi8(static_cast(0x0f)); + + const auto index1 = _mm512_and_si512(_mm512_srli_epi16(prev_1, 4), v_0f); + const auto index2 = _mm512_and_si512(prev_1, v_0f); + const auto index3 = _mm512_and_si512(_mm512_srli_epi16(data, 4), v_0f); + + const auto byte_1_high = _mm512_shuffle_epi8(mask1, index1); + const auto byte_1_low = _mm512_shuffle_epi8(mask2, index2); + const auto byte_2_high = _mm512_shuffle_epi8(mask3, index3); + + return _mm512_ternarylogic_epi64(byte_1_high, byte_1_low, byte_2_high, 128); + }(); + + // multibyte length + const auto length = [data, prev_data, source]() noexcept -> data_type + { + const auto v_7f = _mm512_set1_epi8(static_cast(0x7f)); + const auto v_80 = _mm512_set1_epi8(static_cast(0x80)); + + const auto prev_2 = avx512_utf8_checker::prev<2>(data, prev_data); + const auto prev_3 = avx512_utf8_checker::prev<3>(data, prev_data); + + // Only 111????? will be > 0 + const auto third = _mm512_subs_epu8(prev_2, _mm512_set1_epi8(static_cast(0b1110'0000 - 1))); + // Only 1111???? will be > 0 + const auto fourth = _mm512_subs_epu8(prev_3, _mm512_set1_epi8(static_cast(0b1111'0000 - 1))); + const auto third_or_fourth = _mm512_or_si512(third, fourth); + + return _mm512_ternarylogic_epi32(_mm512_adds_epu8(v_7f, third_or_fourth), v_80, source, 0b110'1010); + }(); + + error = _mm512_or_si512(length, error); + } + + // Return nonzero if there are incomplete multibyte characters at the end of the block: + // e.g. if there is a 4-byte character, but it's 3 bytes from the end. + auto check_incomplete(const data_type data) noexcept -> void + { + // If the previous input's last 3 bytes match this, they're too short (they ended at EOF): + // ... 1111???? 111????? 11?????? + const auto max_value = _mm512_setr_epi64( + static_cast(0xffff'ffff'ffff'ffff), + static_cast(0xffff'ffff'ffff'ffff), + static_cast(0xffff'ffff'ffff'ffff), + static_cast(0xffff'ffff'ffff'ffff), + static_cast(0xffff'ffff'ffff'ffff), + static_cast(0xffff'ffff'ffff'ffff), + static_cast(0xffff'ffff'ffff'ffff), + static_cast(0xbfdf'efff'ffff'ffff) + ); + + prev_incomplete = _mm512_subs_epu8(data, max_value); + } + + public: + auto check_eof() noexcept -> void + { + // The only problem that can happen at EOF is that a multibyte character is too short + // or a byte value too large in the last bytes: check_utf8_bytes::check_special_cases only + // checks for bytes too large in the first of two bytes. + + // If the previous block had incomplete UTF-8 characters at the end, an ASCII block can't + // possibly finish them. + error = _mm512_or_si512(error, prev_incomplete); + } + + [[nodiscard]] bool has_error() const noexcept + { + return _mm512_test_epi8_mask(error, error) != 0; + } + + // returns true if ASCII + [[nodiscard]] auto check_data(const data_type data) noexcept -> bool + { + const auto v_80 = _mm512_set1_epi8(static_cast(0x80)); + + if (const auto ascii = _mm512_test_epi8_mask(data, v_80); + ascii == 0) + { + check_eof(); + return true; + } + + check_utf8_bytes(data, prev_data_block); + check_incomplete(data); + prev_data_block = data; + return false; + } + }; +} + +#endif diff --git a/src/chars/encoding.ixx b/src/chars/encoding.ixx deleted file mode 100644 index 99dfe079..00000000 --- a/src/chars/encoding.ixx +++ /dev/null @@ -1,335 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE - -#include - -export module gal.prometheus.chars:encoding; - -import std; -import gal.prometheus.meta; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -#else -#pragma once - -#include - -#include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -#endif - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::chars) -{ - enum class EncodingType - { - UNKNOWN = 0b0000'0000, - - // BOM 0xef 0xbb 0xbf - UTF8 = 0b0000'0001, - // BOM 0xff 0xfe - UTF16_LE = 0b0000'0010, - // BOM 0xfe 0xff - UTF16_BE = 0b0000'0100, - // BOM 0xff 0xfe 0x00 0x00 - UTF32_LE = 0b0000'1000, - // BOM 0x00 0x00 0xfe 0xff - UTF32_BE, - }; - - [[nodiscard]] constexpr auto width_of(const EncodingType type) noexcept -> std::size_t - { - switch (type) - { - case EncodingType::UNKNOWN: { return 0; } - case EncodingType::UTF8: { return 3; } - case EncodingType::UTF16_LE: - case EncodingType::UTF16_BE: { return 2; } - case EncodingType::UTF32_LE: - case EncodingType::UTF32_BE: { return 4; } - default: { GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); } // NOLINT(clang-diagnostic-covered-switch-default) - } - } - - [[nodiscard]] constexpr auto bom_of(const std::span string) noexcept -> EncodingType - { - // https://en.wikipedia.org/wiki/Byte_order_mark#Byte-order_marks_by_encoding - - const auto length = string.size(); - if (length < 2) - { - return EncodingType::UNKNOWN; - } - - if (string[0] == 0xff and string[1] == 0xfe) - { - if (length >= 4 and string[2] == 0x00 and string[3] == 0x00) - { - return EncodingType::UTF32_LE; - } - return EncodingType::UTF16_LE; - } - - if (string[0] == 0xfe and string[1] == 0xff) - { - return EncodingType::UTF16_BE; - } - - if (length >= 4 and string[0] == 0x00 and string[1] == 0x00 and string[2] == 0xfe and string[3] == 0xff) - { - return EncodingType::UTF32_BE; - } - - if (length >= 3 and string[0] == 0xef and string[1] == 0xbb and string[2] == 0xbf) - { - return EncodingType::UTF8; - } - - return EncodingType::UNKNOWN; - } - - [[nodiscard]] constexpr auto bom_of(const std::span string) noexcept -> EncodingType - { - static_assert(sizeof(char) == sizeof(char8_t)); - - const auto* char8_string = GAL_PROMETHEUS_SEMANTIC_UNRESTRICTED_CHAR_POINTER_CAST(char8_t, string.data()); - return bom_of({char8_string, string.size()}); - } - - enum class ErrorCode - { - NONE = 0, - - // The decoded character must be not in U+D800...DFFF (UTF-8 or UTF-32) OR - // a high surrogate must be followed by a low surrogate and a low surrogate must be preceded by a high surrogate (UTF-16) OR - // there must be no surrogate at all (Latin1) - SURROGATE, - - // The leading byte must be followed by N-1 continuation bytes, where N is the UTF-8 character length. - // This is also the error when the input is truncated. - TOO_SHORT, - // We either have too many consecutive continuation bytes or the string starts with a continuation byte. - TOO_LONG, - // The decoded character must be above U+7F for two-byte characters, U+7FF for three-byte characters, and U+FFFF for four-byte characters. - OVERLONG, - // The decoded character must be less than or equal to U+10FFFF, less than or equal than U+7F for ASCII OR less than equal than U+FF for Latin1 - TOO_LARGE, - // Any byte must have fewer than 5 header bits. - HEADER_BITS, - }; - - struct result_type - { - ErrorCode error; - // In case of error, indicates the position of the error. - // In case of success, indicates the number of code units validated/written. - std::size_t count; - - [[nodiscard]] constexpr auto has_error() const noexcept -> bool { return error != ErrorCode::NONE; } - - [[nodiscard]] constexpr explicit operator bool() const noexcept { return not has_error(); } - }; - - enum class CharsCategory - { - ASCII, - - UTF8_CHAR, - UTF8, - - UTF16_LE, - UTF16_BE, - UTF16, - - UTF32, - }; - - template - struct input_selector; - - template - struct output_selector; - - // ====================================== - // ASCII - // ====================================== - - template<> - struct input_selector - { - using type = std::span; - constexpr static auto value = CharsCategory::ASCII; - }; - - template<> - struct output_selector - { - using type = std::span; - constexpr static auto value = CharsCategory::ASCII; - }; - - // ====================================== - // UTF8_CHAR - // ====================================== - - template<> - struct input_selector - { - using type = std::span; - constexpr static auto value = CharsCategory::UTF8_CHAR; - }; - - template<> - struct output_selector - { - using type = std::span; - constexpr static auto value = CharsCategory::UTF8_CHAR; - }; - - // ====================================== - // UTF8 - // ====================================== - - template<> - struct input_selector - { - using type = std::span; - constexpr static auto value = CharsCategory::UTF8; - }; - - template<> - struct output_selector - { - using type = std::span; - constexpr static auto value = CharsCategory::UTF8; - }; - - // ====================================== - // UTF16_LE - // ====================================== - - template<> - struct input_selector - { - using type = std::span; - constexpr static auto value = CharsCategory::UTF16_LE; - }; - - template<> - struct output_selector - { - using type = std::span; - constexpr static auto value = CharsCategory::UTF16_LE; - }; - - // ====================================== - // UTF16_BE - // ====================================== - - template<> - struct input_selector - { - using type = std::span; - constexpr static auto value = CharsCategory::UTF16_BE; - }; - - template<> - struct output_selector - { - using type = std::span; - constexpr static auto value = CharsCategory::UTF16_BE; - }; - - // ====================================== - // UTF16 - // ====================================== - - template<> - struct input_selector - { - using type = std::span; - constexpr static auto value = CharsCategory::UTF16; - }; - - template<> - struct output_selector - { - using type = std::span; - constexpr static auto value = CharsCategory::UTF16; - }; - - // ====================================== - // UTF32 - // ====================================== - - template<> - struct input_selector - { - using type = std::span; - constexpr static auto value = CharsCategory::UTF32; - }; - - template<> - struct output_selector - { - using type = std::span; - constexpr static auto value = CharsCategory::UTF32; - }; - - template - struct chars_category_of; - - template - requires requires - { - { - T::value - } -> std::same_as; - } - struct chars_category_of - { - constexpr static auto value = T::value; - }; - - template - constexpr auto chars_category_of_v = chars_category_of::value; - - template - using input_type = typename input_selector::type; - - template - using output_type = typename output_selector::type; - - enum class InputProcessPolicy - { - ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT, - RETURN_RESULT_TYPE, - ASSUME_VALID_INPUT, - }; - - template - class Scalar; - - template - class Simd; - - template - class Encoding; - - template - struct scalar_processor_of; - - template - using scalar_processor_of_t = typename scalar_processor_of::type; - - template - struct simd_processor_of; - - template - using simd_processor_of_t = typename simd_processor_of::type; -} diff --git a/src/chars/icelake.cpp b/src/chars/icelake.cpp new file mode 100644 index 00000000..3000873d --- /dev/null +++ b/src/chars/icelake.cpp @@ -0,0 +1,6342 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + +#include + +#include +#include + +#if __has_include() +#include +#endif +#if __has_include() +#include +#endif + +#include + +#include +#include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +#include + +namespace +{ + using namespace gal::prometheus; + using namespace chars; + + using data_type = __m512i; + + namespace common + { + template + requires ( + InputType == CharsType::LATIN or + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto sign_of(const data_type data) noexcept -> auto + { + struct sign_type + { + private: + __mmask64 mask_; + + public: + constexpr explicit sign_type(const __mmask64 mask) noexcept + : mask_{mask} {} + + /** + * @brief Get the underlying mask of the current block. + */ + [[nodiscard]] constexpr auto mask() const noexcept -> __mmask64 + { + return mask_; + } + + /** + * @brief Whether all sign bits are 0, in other words, whether the current block is all ASCII. + */ + [[nodiscard]] constexpr auto pure() const noexcept -> bool + { + return mask_ == 0; + } + + /** + * @brief Get the number of non-ASCII in the current block. + */ + [[nodiscard]] constexpr auto count() const noexcept -> std::size_t + { + return std::popcount(mask_); + } + + /** + * @brief Get the number of consecutive ASCII at the beginning. + * [ascii] [non-ascii] [?] [?] ... Xn ... [?] [?] [ascii] [ascii] + * ^-----^ start_count + * ^----------^ end_count + */ + [[nodiscard]] constexpr auto start_count() const noexcept -> std::size_t + { + return std::countr_zero(mask_); + } + + /** + * @brief Get the number of consecutive ASCII at the ending. + * [ascii] [non-ascii] [?] [?] ... Xn ... [?] [?] [ascii] [ascii] + * ^-----^ start_count + * ^----------^ end_count + */ + [[nodiscard]] constexpr auto end_count() const noexcept -> std::size_t + { + return std::countl_zero(mask_); + } + }; + + return sign_type{_mm512_movepi8_mask(data)}; + } + + template + requires ( + Type == CharsType::UTF16_LE or + Type == CharsType::UTF16_BE or + Type == CharsType::UTF16 + ) + [[nodiscard]] constexpr auto not_native_endian() noexcept -> bool + { + return Type == CharsType::UTF16 or (Type == CharsType::UTF16_LE) != (std::endian::native == std::endian::little); + } + } +} + +#include +#include + +namespace +{ + namespace latin + { + using input_type = chars::latin::input_type; + // using char_type = chars::latin::char_type; + using size_type = chars::latin::size_type; + using pointer_type = chars::latin::pointer_type; + + constexpr auto advance_of_latin = sizeof(data_type) / sizeof(input_type_of::value_type); + constexpr auto advance_of_utf8 = sizeof(data_type) / sizeof(input_type_of::value_type); + constexpr auto advance_of_utf16 = sizeof(data_type) / sizeof(input_type_of::value_type); + constexpr auto advance_of_utf32 = sizeof(data_type) / sizeof(input_type_of::value_type); + + static_assert(advance_of_utf8 == sizeof(data_type) / sizeof(input_type_of::value_type)); + static_assert(advance_of_utf16 == sizeof(data_type) / sizeof(input_type_of::value_type)); + static_assert(advance_of_utf16 == sizeof(data_type) / sizeof(input_type_of::value_type)); + + template + requires ( + InputType == CharsType::UTF16_LE or + InputType == CharsType::UTF16_BE + ) + [[nodiscard]] constexpr auto to_native_utf16(const data_type data) noexcept -> data_type + { + if constexpr (common::not_native_endian()) + { + const auto byte_flip = _mm512_setr_epi64( + // clang-format off + 0x0607'0405'0203'0001, 0x0e0f'0c0d'0a0b'0809, + 0x0607'0405'0203'0001, 0x0e0f'0c0d'0a0b'0809, + 0x0607'0405'0203'0001, 0x0e0f'0c0d'0a0b'0809, + 0x0607'0405'0203'0001, 0x0e0f'0c0d'0a0b'0809 + // clang-format on + ); + + return _mm512_shuffle_epi8(data, byte_flip); + } + else + { + return data; + } + } + + namespace icelake + { + [[nodiscard]] constexpr auto validate(const input_type input) noexcept -> result_error_input_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + while (it_input_current + advance_of_latin <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_latin}; + #endif + + const auto data = _mm512_loadu_si512(it_input_current); + + if (const auto sign = common::sign_of(data); + not sign.pure()) + { + it_input_current += sign.start_count(); + + const auto current_input_length = static_cast(it_input_current - it_input_begin); + + return {.error = ErrorCode::TOO_LARGE, .input = current_input_length}; + } + + it_input_current += advance_of_latin; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_latin); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto mask = _bzhi_u64(~static_cast(0), static_cast(remaining)); + const auto data = _mm512_maskz_loadu_epi8(mask, it_input_current); + + if (const auto sign = common::sign_of(data); + not sign.pure()) + { + it_input_current += sign.start_count(); + + const auto current_input_length = static_cast(it_input_current - it_input_begin); + + return {.error = ErrorCode::TOO_LARGE, .input = current_input_length}; + } + + it_input_current += remaining; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + return {.error = ErrorCode::NONE, .input = current_input_length}; + } + + template + [[nodiscard]] constexpr auto length(const input_type input) noexcept -> size_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + + // ReSharper disable CppClangTidyBugproneBranchClone + if constexpr (OutputType == CharsType::LATIN) + { + return input.size(); + } + // ReSharper restore CppClangTidyBugproneBranchClone + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + // number of 512-bit chunks that fits into the length + size_type output_length = input_length / advance_of_utf8 * advance_of_utf8; + + while (it_input_current + advance_of_utf8 <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_utf8}; + #endif + + const auto data = _mm512_loadu_si512(it_input_current); + + if (const auto sign = common::sign_of(data); + not sign.pure()) + { + output_length += sign.count(); + } + + it_input_current += advance_of_utf8; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_utf8); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + // fallback + output_length += Scalar::length({it_input_current, static_cast(remaining)}); + } + + return output_length; + } + // ReSharper disable CppClangTidyBugproneBranchClone + else if constexpr (OutputType == CharsType::UTF16_LE or OutputType == CharsType::UTF16_BE or OutputType == CharsType::UTF16) + { + return input.size(); + } + else if constexpr (OutputType == CharsType::UTF32) + { + return input.size(); + } + // ReSharper restore CppClangTidyBugproneBranchClone + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template + requires ( + OutputType == CharsType::UTF8_CHAR or + OutputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto write_utf8( + const typename output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + std::ignore = Correct; + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + const auto transform = [&](const data_type data, const std::size_t data_length) noexcept -> void + { + if constexpr (not MaskOut) + { + GAL_PROMETHEUS_ERROR_ASSUME(data_length == static_cast(advance_of_utf8)); + } + + const auto sign = common::sign_of(data); + + const auto non_ascii = sign.mask(); + const auto non_ascii_high = static_cast(non_ascii >> 32); + const auto non_ascii_low = static_cast(non_ascii); + + const auto ascii = ~non_ascii; + const auto ascii_high = static_cast(static_cast(ascii >> 32)); + const auto ascii_low = static_cast(static_cast(ascii)); + + // Here we invert it (~) to generate a final mask used to compress only the needed bytes, + // the bits in ascii are inverted and zeros are interspersed in between them. + constexpr auto alternate_bits = 0x5555'5555'5555'5555ull; + const auto mask_high = ~_pdep_u64(ascii_high, alternate_bits); + const auto mask_low = ~_pdep_u64(ascii_low, alternate_bits); + + // Interleave bytes from top and bottom halves. + // We permute the 64-byte 'data' so that we can later apply + // different transformations on the lower 32 bytes and the upper 32 bytes. + // + // _mm512_permutexvar_epi8 lets us reorder the bytes in 'data' based on + // a specified permutation vector. + // + // In this particular permutation, the goal is to transform an original layout + // (e.g., [a0, a1, ..., a31, b0, b1, ..., b31]) into an interleaved layout + // ([a0, b0, a1, b1, ..., a31, b31]) or a similar pattern that suits + // the subsequent processing steps. + const auto source_interleaved = _mm512_permutexvar_epi8( + _mm512_set_epi32( + // clang-format off + 0x3f1f'3e1e, 0x3d1d'3c1c, 0x3b1b'3a1a, 0x3919'3818, + 0x3717'3616, 0x3515'3414, 0x3313'3212, 0x3111'3010, + 0x2f0f'2e0e, 0x2d0d'2c0c, 0x2b0b'2a0a, 0x2909'2808, + 0x2707'2606, 0x2505'2404, 0x2303'2202, 0x2101'2000 + // clang-format on + ), + data + ); + + // Mask to denote whether the byte is a leading byte that is not ascii + // binary representation of 192: 1100'0000 + const auto sixth = _mm512_cmpge_epu8_mask(data, _mm512_set1_epi8(static_cast(192))); + const auto sixth_high = static_cast<__mmask32>(sixth >> 32); + const auto sixth_low = static_cast<__mmask32>(sixth); + + const auto output_low = [](const data_type interleaved, const __mmask32 s, const __mmask64 mask) noexcept -> auto + { + // Upscale the bytes to 16-bit value, adding the 0b1100'0010 leading byte in the process. + // We adjust for the bytes that have their two most significant bits. + // This takes care of the first 32 bytes, assuming we interleaved the bytes. + // binary representation of 194: 1100'0010 + auto v = _mm512_shldi_epi16(interleaved, _mm512_set1_epi8(static_cast(194)), 8); + v = _mm512_mask_add_epi16( + v, + s, + v, + // 1- 0x4000 = 1100 0000 0000 0001 + _mm512_set1_epi16(1 - 0x4000) + ); + // prune redundant bytes + return _mm512_maskz_compress_epi8(mask, v); + }(source_interleaved, sixth_low, mask_low); + + const auto output_high = [](const data_type interleaved, const __mmask32 s, const __mmask64 mask) noexcept -> auto + { + // in the second 32-bit half, set first or second option based on whether original input is leading byte (second case) or not (first case). + const auto leading = _mm512_mask_blend_epi16( + s, + // 0000 0000 1101 0010 + _mm512_set1_epi16(0x00c2), + // 0100 0000 1100 0011 + _mm512_set1_epi16(0x40c3) + ); + const auto v = _mm512_ternarylogic_epi32( + interleaved, + leading, + _mm512_set1_epi16(static_cast(0xff00)), + // (interleaved & 0xff00) ^ leading + (240 & 170) ^ 204 + ); + // prune redundant bytes + return _mm512_maskz_compress_epi8(mask, v); + }(source_interleaved, sixth_high, mask_high); + + const auto length_total = static_cast(data_length + std::popcount(non_ascii)); + + if constexpr (MaskOut) + { + // is the second half of the input vector used? + if (data_length <= 32) + { + const auto mask = _bzhi_u64(~static_cast(0), length_total); + _mm512_mask_storeu_epi8(it_output_current + 0, mask, output_low); + + it_input_current += data_length; + it_output_current += length_total; + + return; + } + } + + const auto low_length = 32 + static_cast(std::popcount(non_ascii_low)); + // const auto high_length = static_cast(data_length - 32) + static_cast(std::popcount(non_ascii_high)); + std::ignore = non_ascii_high; + const auto high_length = length_total - low_length; + + const auto low_mask = _bzhi_u64(~static_cast(0), low_length); + const auto high_mask = _bzhi_u64(~static_cast(0), high_length); + + if constexpr (MaskOut) + { + _mm512_mask_storeu_epi8(it_output_current + 0, low_mask, output_low); + } + else + { + _mm512_storeu_si512(it_output_current + 0, output_low); + } + _mm512_mask_storeu_epi8(it_output_current + low_length, high_mask, output_high); + + it_input_current += data_length; + it_output_current += length_total; + }; + + // if there's at least 128 bytes remaining, we don't need to mask the output + while (it_input_current + 2 * advance_of_utf8 <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_utf8}; + #endif + + const auto data = _mm512_loadu_si512(it_input_current); + + if constexpr (Pure) + { + _mm512_storeu_si512(it_output_current, data); + + it_input_current += advance_of_utf8; + it_output_current += advance_of_utf8; + } + else + { + if (const auto sign = common::sign_of(data); + sign.pure()) + { + _mm512_storeu_si512(it_output_current, data); + + it_input_current += advance_of_utf8; + it_output_current += advance_of_utf8; + } + else + { + transform.template operator()(data, advance_of_utf8); + } + } + } + + // in the last 128 bytes, the first 64 may require masking the output + if (it_input_current + advance_of_utf8 <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_utf8}; + #endif + + const auto data = _mm512_loadu_si512(it_input_current); + + if constexpr (Pure) + { + _mm512_storeu_si512(it_output_current, data); + + it_input_current += advance_of_utf8; + it_output_current += advance_of_utf8; + } + else + { + transform.template operator()(data, advance_of_utf8); + } + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_utf8); + + // with the last 64 bytes, the input also needs to be masked + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto mask = _bzhi_u64(~static_cast(0), static_cast(remaining)); + const auto data = _mm512_maskz_loadu_epi8(mask, it_input_current); + + if constexpr (Pure) + { + const auto out_mask = _bzhi_u64(~static_cast(0), static_cast(remaining)); + _mm512_mask_storeu_epi8(it_output_current, out_mask, data); + + it_input_current += remaining; + it_output_current += remaining; + } + else + { + transform.template operator()(data, remaining); + } + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + template + requires ( + OutputType == CharsType::UTF16_LE or + OutputType == CharsType::UTF16_BE + ) + [[nodiscard]] constexpr auto write_utf16( + const typename output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + std::ignore = Pure; + std::ignore = Correct; + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + while (it_input_current + advance_of_utf16 <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_utf16}; + #endif + + // Load 32 Latin1 characters into a 256-bit register + const auto m256 = _mm256_loadu_si256( + GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST( + const __m256i *, + it_input_current + ) + ); + // Zero extend each set of 8-bit characters to 32 16-bit integers + const auto data = _mm512_cvtepu8_epi16(m256); + + const auto native_data = latin::to_native_utf16(data); + _mm512_storeu_si512(it_output_current, native_data); + + it_input_current += advance_of_utf16; + it_output_current += advance_of_utf16; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_utf16); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto mask = _bzhi_u32(~static_cast(0), static_cast(remaining)); + const auto m256 = _mm256_maskz_loadu_epi8(mask, it_input_current); + // Zero extend each set of 8-bit characters to 32 16-bit integers + const auto data = _mm512_cvtepu8_epi16(m256); + + const auto native_data = latin::to_native_utf16(data); + _mm512_mask_storeu_epi16(it_output_current, mask, native_data); + + it_input_current += remaining; + it_output_current += remaining; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + template + requires ( + OutputType == CharsType::UTF32 + ) + [[nodiscard]] constexpr auto write_utf32( + const typename output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + std::ignore = Pure; + std::ignore = Correct; + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + while (it_input_current + advance_of_utf32 <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_utf32}; + #endif + + // Load 16 Latin1 characters into a 128-bit register + const auto m128 = _mm_loadu_si128( + GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST( + const __m128i *, + it_input_current + ) + ); + // Zero extend each set of 8-bit characters to 16 32-bit integers + const auto data = _mm512_cvtepu8_epi32(m128); + + _mm512_storeu_si512(it_output_current, data); + + it_input_current += advance_of_utf32; + it_output_current += advance_of_utf32; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_utf32); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto mask = static_cast<__mmask16>(_bzhi_u32(~static_cast(0), static_cast(remaining))); + const auto m128 = _mm_maskz_loadu_epi8(mask, it_input_current); + // Zero extend each set of 8-bit characters to 16 32-bit integers + const auto data = _mm512_cvtepu8_epi32(m128); + + _mm512_mask_storeu_epi32(it_output_current, mask, data); + + it_input_current += remaining; + it_output_current += remaining; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + } + } + + namespace utf8 + { + constexpr auto advance_of_latin = sizeof(data_type) / sizeof(input_type_of::value_type); + constexpr auto advance_of_utf8 = sizeof(data_type) / sizeof(input_type_of::value_type); + constexpr auto advance_of_utf16 = sizeof(data_type) / sizeof(input_type_of::value_type); + constexpr auto advance_of_utf32 = sizeof(data_type) / sizeof(input_type_of::value_type); + + static_assert(advance_of_utf8 == sizeof(data_type) / sizeof(input_type_of::value_type)); + static_assert(advance_of_utf16 == sizeof(data_type) / sizeof(input_type_of::value_type)); + static_assert(advance_of_utf16 == sizeof(data_type) / sizeof(input_type_of::value_type)); + + template + requires ( + InputType == CharsType::UTF16_LE or + InputType == CharsType::UTF16_BE + ) + [[nodiscard]] constexpr auto to_native_utf16(const data_type data) noexcept -> data_type + { + if constexpr (common::not_native_endian()) + { + const auto byte_flip = _mm512_setr_epi64( + // clang-format off + 0x0607'0405'0203'0001, 0x0e0f'0c0d'0a0b'0809, + 0x0607'0405'0203'0001, 0x0e0f'0c0d'0a0b'0809, + 0x0607'0405'0203'0001, 0x0e0f'0c0d'0a0b'0809, + 0x0607'0405'0203'0001, 0x0e0f'0c0d'0a0b'0809 + // clang-format on + ); + + return _mm512_shuffle_epi8(data, byte_flip); + } + else + { + return data; + } + } + + namespace icelake + { + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto validate(const input_type_of input) noexcept -> result_error_input_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + + using input_type = input_type_of; + using pointer_type = typename input_type::const_pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + detail::utf8::icelake::avx512_utf8_checker checker{}; + + const auto do_fallback = [&]() noexcept -> result_error_input_type + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + + if (it_input_current == it_input_begin) + [[unlikely]] + { + const auto result = Scalar::validate({it_input_current, it_input_end}); + return {.error = result.error, .input = current_input_length + result.input}; + } + + const auto result = [=, current = it_input_current - 1]() noexcept + { + if constexpr (InputType == CharsType::UTF8_CHAR) + { + return utf8_char::scalar::rewind_and_validate(it_input_begin, current, it_input_end); + } + else + { + return chars::utf8::scalar::rewind_and_validate(it_input_begin, current, it_input_end); + } + }(); + return {.error = result.error, .input = result.input + current_input_length}; + }; + + while (it_input_current + advance_of_utf8 <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_utf8}; + #endif + + if (const auto data = _mm512_loadu_si512(it_input_current); + checker.check_data(data)) + { + it_input_current += advance_of_utf8; + continue; + } + + if (checker.has_error()) + { + return do_fallback(); + } + + it_input_current += advance_of_utf8; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_utf8); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto mask = _bzhi_u64(~static_cast(0), static_cast(remaining)); + if (const auto data = _mm512_maskz_loadu_epi8(mask, it_input_current); + not checker.check_data(data) and checker.has_error()) + { + return do_fallback(); + } + + it_input_current += remaining; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + return {.error = ErrorCode::NONE, .input = current_input_length}; + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto length(const input_type_of input) noexcept -> typename input_type_of::size_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + + using input_type = input_type_of; + using pointer_type = typename input_type::const_pointer; + using size_type = typename input_type::size_type; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + constexpr auto advance = advance_of_latin; + static_assert(advance == sizeof(__m512i)); + + if constexpr (OutputType == CharsType::LATIN) + { + const auto continuation = _mm512_set1_epi8(static_cast(0b1011'1111)); + + auto unrolled_length = _mm512_setzero_si512(); + // number of 512-bit chunks that fits into the length + auto result_length = static_cast(input_length / advance * advance); + + while (it_input_current + advance <= it_input_end) + { + const auto iterations = static_cast((it_input_end - it_input_current) / advance); + const auto this_turn_end = it_input_current + iterations * advance - advance; + + while (it_input_current + 8 * advance <= this_turn_end) + { + const auto* p = GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m512i *, it_input_current); + + const auto data_0 = _mm512_loadu_si512(p + 0); + const auto data_1 = _mm512_loadu_si512(p + 1); + const auto data_2 = _mm512_loadu_si512(p + 2); + const auto data_3 = _mm512_loadu_si512(p + 3); + const auto data_4 = _mm512_loadu_si512(p + 4); + const auto data_5 = _mm512_loadu_si512(p + 5); + const auto data_6 = _mm512_loadu_si512(p + 6); + const auto data_7 = _mm512_loadu_si512(p + 7); + + const auto mask_0 = _mm512_cmple_epi8_mask(data_0, continuation); + const auto mask_1 = _mm512_cmple_epi8_mask(data_1, continuation); + const auto mask_2 = _mm512_cmple_epi8_mask(data_2, continuation); + const auto mask_3 = _mm512_cmple_epi8_mask(data_3, continuation); + const auto mask_4 = _mm512_cmple_epi8_mask(data_4, continuation); + const auto mask_5 = _mm512_cmple_epi8_mask(data_5, continuation); + const auto mask_6 = _mm512_cmple_epi8_mask(data_6, continuation); + const auto mask_7 = _mm512_cmple_epi8_mask(data_7, continuation); + + const auto mask_register = _mm512_set_epi64(mask_7, mask_6, mask_5, mask_4, mask_3, mask_2, mask_1, mask_0); + + unrolled_length = _mm512_add_epi64(unrolled_length, _mm512_popcnt_epi64(mask_register)); + + it_input_current += 8 * advance; + } + + while (it_input_current <= this_turn_end) + { + const auto* p = GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m512i *, it_input_current); + + const auto data = _mm512_loadu_si512(p); + + const auto continuation_bitmask = _mm512_cmple_epi8_mask(data, continuation); + result_length -= std::popcount(continuation_bitmask); + + it_input_current += advance; + } + } + result_length -= _mm512_reduce_add_epi64(unrolled_length); + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance); + + if (remaining != 0) + { + result_length += Scalar::length({it_input_current, static_cast(remaining)}); + } + + return result_length; + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + return input.size(); + } + else if constexpr (OutputType == CharsType::UTF16_LE or OutputType == CharsType::UTF16_BE or OutputType == CharsType::UTF16) + { + auto result_length = static_cast(0); + while (it_input_current + advance <= it_input_end) + { + const auto data = _mm512_loadu_si512(it_input_current); + + const auto utf8_continuation_mask = _mm512_cmple_epi8_mask(data, _mm512_set1_epi8(-64)); + // We count one word for anything that is not a continuation (so leading bytes) + result_length += advance - std::popcount(utf8_continuation_mask); + + const auto utf8_4_byte = _mm512_cmpge_epu8_mask(data, _mm512_set1_epi8(static_cast(240))); + result_length += std::popcount(utf8_4_byte); + + it_input_current += advance; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance); + + if (remaining != 0) + { + // fallback + result_length += Scalar::length({it_input_current, static_cast(remaining)}); + } + + return result_length; + } + else if constexpr (OutputType == CharsType::UTF32) + { + return icelake::length(input); + } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) and + ( + OutputType == CharsType::LATIN + ) + [[nodiscard]] constexpr auto write_latin( + const typename output_type_of::pointer output, + const input_type_of input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using input_type = input_type_of; + using pointer_type = typename input_type::const_pointer; + using size_type = typename input_type::size_type; + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + const auto do_process = [&]( + const pointer_type current, + const size_type length, + __mmask64& out_next_leading, + __mmask64& out_next_bit6 + ) noexcept -> __mmask64 + { + if constexpr (not MaskOut) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length == advance_of_latin); + } + if constexpr (Pure) + { + std::ignore = out_next_leading; + std::ignore = out_next_bit6; + } + + // 1100 0000 + const auto v_minus64 = _mm512_set1_epi8(-64); + // 1100 0010 + const auto v_minus62 = _mm512_set1_epi8(-62); + const auto v_0001 = _mm512_set1_epi8(1); + + const auto mask = _bzhi_u64(~static_cast(0), static_cast(length)); + const auto data = [=]() noexcept -> data_type + { + if constexpr (MaskOut) + { + return _mm512_maskz_loadu_epi8(mask, current); + } + else + { + return _mm512_loadu_si512(current); + } + + // warning: no return statement in function returning non-void [-Wreturn-type] + #if defined(GAL_PROMETHEUS_COMPILER_GNU) + GAL_PROMETHEUS_ERROR_UNREACHABLE(); + #endif + }(); + + const auto write_pure = [&]() noexcept -> void + { + if constexpr (MaskOut) + { + _mm512_mask_storeu_epi8(it_output_current, mask, data); + } + else + { + _mm512_storeu_si512(it_output_current, data); + } + + it_output_current += length; + }; + + if constexpr (Pure) + { + write_pure(); + return 0; + } + else + { + const auto non_ascii = _mm512_movepi8_mask(data); + if (non_ascii == 0) + { + write_pure(); + return 0; + } + + const auto leading = _mm512_cmpge_epu8_mask(data, v_minus64); + const auto high_bits = _mm512_xor_si512(data, v_minus62); + + if constexpr (not Correct) + { + if (const auto invalid_leading_bytes = _mm512_mask_cmpgt_epu8_mask(leading, high_bits, v_0001); + invalid_leading_bytes) + { + return invalid_leading_bytes; + } + + if (const auto leading_shift = (leading << 1) | out_next_leading; + (non_ascii ^ leading) != leading_shift) + { + return leading_shift; + } + } + + const auto bit6 = _mm512_cmpeq_epi8_mask(high_bits, v_0001); + const auto sub = _mm512_mask_sub_epi8(data, (bit6 << 1) | out_next_bit6, data, v_minus64); + const auto retain = ~leading & mask; + const auto num_out = std::popcount(retain); + + const auto out_mask = _bzhi_u64(~static_cast(0), static_cast(num_out)); + const auto out = _mm512_maskz_compress_epi8(retain, sub); + + _mm512_mask_storeu_epi8(it_output_current, out_mask, out); + + it_output_current += num_out; + out_next_leading = leading >> 63; + out_next_bit6 = bit6 >> 63; + + return 0; + } + }; + const auto do_fallback = [&](const __mmask64 mask) noexcept -> result_error_input_output_type + { + const auto extra_valid = static_cast(std::countr_zero(mask)); + const auto result_valid = Scalar::convert(it_output_current, {it_input_current, extra_valid}); + + it_input_current += extra_valid; + it_output_current += result_valid.output; + + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + // fixme + const auto result = Scalar::convert(it_output_current, {it_input_current, it_input_end}); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(result.has_error()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(result.input == 0); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(result.output == 0); + + return {.error = result.error, .input = current_input_length + result.input, .output = current_output_length + result.output}; + }; + + __mmask64 next_leading = 0; + __mmask64 next_bit6 = 0; + while (it_input_current + advance_of_latin <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_latin}; + #endif + + const auto mask = do_process.template operator()(it_input_current, advance_of_latin, next_leading, next_bit6); + + if constexpr (Pure or Correct) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(mask == 0); + } + else + { + if (mask != 0) + { + return do_fallback(mask); + } + } + + it_input_current += advance_of_latin; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_latin); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto mask = do_process.template operator()(it_input_current, static_cast(remaining), next_leading, next_bit6); + + if constexpr (Pure or Correct) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(mask == 0); + } + else + { + if (mask != 0) + { + return do_fallback(mask); + } + } + + it_input_current += remaining; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) and + ( + OutputType == CharsType::UTF16_LE or + OutputType == CharsType::UTF16_BE + ) + [[nodiscard]] constexpr auto write_utf16( + const typename output_type_of::pointer output, + const input_type_of input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using input_type = input_type_of; + using pointer_type = typename input_type::const_pointer; + using size_type = typename input_type::size_type; + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + constexpr auto advance = 2 * advance_of_utf16; + + const auto do_process = [&]() noexcept + { + const auto mask_identity = _mm512_set_epi8( + // clang-format off + 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, + 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, + 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, + 15, 14, 13, 12, 11, 10, 9u, 8u, 07, 06, 05, 04, 03, 02, 01, 00 + // clang-format on + ); + + // ReSharper disable CppInconsistentNaming + // ReSharper disable IdentifierTypo + + const auto v_c0c0_c0c0 = _mm512_set1_epi32(static_cast(0xc0c0'c0c0)); + const auto v_0400_0400 = _mm512_set1_epi32(0x0400'0400); + const auto v_8080_8080 = _mm512_set1_epi32(static_cast(0x8080'8080)); + const auto v_0800_0800 = _mm512_set1_epi32(0x0800'0800); + const auto v_d800_d800 = _mm512_set1_epi32(static_cast(0xd800'd800)); + const auto v_f0f0_f0f0 = _mm512_set1_epi32(static_cast(0xf0f0'f0f0)); + const auto v_dfdf_dfdf = _mm512_set_epi64( + static_cast(0xffff'dfdf'dfdf'dfdf), + static_cast(0xdfdf'dfdf'dfdf'dfdf), + static_cast(0xdfdf'dfdf'dfdf'dfdf), + static_cast(0xdfdf'dfdf'dfdf'dfdf), + static_cast(0xdfdf'dfdf'dfdf'dfdf), + static_cast(0xdfdf'dfdf'dfdf'dfdf), + static_cast(0xdfdf'dfdf'dfdf'dfdf), + static_cast(0xdfdf'dfdf'dfdf'dfdf) + ); + const auto v_c2c2_c2c2 = _mm512_set1_epi32(static_cast(0xc2c2'c2c2)); + const auto v_ffff_ffff = _mm512_set1_epi32(static_cast(0xffff'ffff)); + const auto v_d7c0_d7c0 = _mm512_set1_epi32(static_cast(0xd7c0'd7c0)); + const auto v_dc00_dc00 = _mm512_set1_epi32(static_cast(0xdc00'dc00)); + + // ReSharper restore IdentifierTypo + // ReSharper restore CppInconsistentNaming + + const auto length = [=]() noexcept + { + if constexpr (MaskOut) + { + return static_cast(it_input_end - it_input_current); + } + else + { + return advance; + } + }(); + + const auto mask = _bzhi_u64(~static_cast(0), static_cast(length)); + const auto data = [=]() noexcept -> data_type + { + if constexpr (MaskOut) + { + return _mm512_maskz_loadu_epi8(mask, it_input_current); + } + else + { + return _mm512_loadu_si512(it_input_current); + } + + // warning: no return statement in function returning non-void [-Wreturn-type] + #if defined(GAL_PROMETHEUS_COMPILER_GNU) + GAL_PROMETHEUS_ERROR_UNREACHABLE(); + #endif + }(); + + const auto write_pure = [&]() noexcept -> void + { + static_assert(advance == 64); + + const auto data_1 = [=]() noexcept -> auto + { + const auto out = _mm512_cvtepu8_epi16(_mm512_castsi512_si256(data)); + const auto native_out = utf8::to_native_utf16(out); + + return native_out; + }(); + + if constexpr (MaskOut) + { + // is the second half of the input vector used? + if (length <= 32) + { + const auto out_mask = _bzhi_u32(~static_cast(0), static_cast(length)); + + _mm512_mask_storeu_epi16(it_output_current, out_mask, data_1); + + it_input_current += length; + it_output_current += length; + + return; + } + } + + const auto data_2 = [=]() noexcept -> auto + { + const auto out = _mm512_cvtepu8_epi16(_mm512_extracti64x4_epi64(data, 1)); + const auto native_out = utf8::to_native_utf16(out); + + return native_out; + }(); + + _mm512_storeu_si512(it_output_current + 0, data_1); + if constexpr (MaskOut) + { + const auto out_mask = _bzhi_u32(~static_cast(0), static_cast(length - 32)); + _mm512_mask_storeu_epi16(it_output_current + 32, out_mask, data_2); + } + else + { + _mm512_storeu_si512(it_output_current + 32, data_2); + } + + it_input_current += length; + it_output_current += length; + }; + + if constexpr (Pure) + { + write_pure(); + + return true; + } + else + { + const auto mask_byte_1 = _mm512_mask_cmplt_epu8_mask(mask, data, v_8080_8080); + // NOT(mask_byte_1) AND valid_input_length -- if all zeroes, then all ASCII + if (_ktestc_mask64_u8(mask_byte_1, mask)) + { + write_pure(); + + return true; + } + + // classify characters further + + // 0xc0 <= in, 2, 3, or 4 leading byte + const auto mask_byte_234 = _mm512_cmple_epu8_mask(v_c0c0_c0c0, data); + // 0xdf < in, 3 or 4 leading byte + const auto mask_byte_34 = _mm512_cmplt_epu8_mask(v_dfdf_dfdf, data); + + if constexpr (not Correct) + { + // 0xc0 <= data < 0xc2 (illegal two byte sequence) + if (const auto two_bytes = _mm512_mask_cmplt_epu8_mask(mask_byte_234, data, v_c2c2_c2c2); + _ktestz_mask64_u8(two_bytes, two_bytes) == 0) + { + // Overlong 2-byte sequence + return false; + } + } + + if (_ktestz_mask64_u8(mask_byte_34, mask_byte_34) == 0) + { + // We have a 3-byte sequence and/or a 2-byte sequence, or possibly even a 4-byte sequence + + // 0xf0 <= zmm0 (4 byte start bytes) + const auto mask_byte_4 = _mm512_cmpge_epu8_mask(data, v_f0f0_f0f0); + const auto mask_not_ascii = [=]() noexcept -> __mmask64 + { + if constexpr (const auto out = _knot_mask64(mask_byte_1); + MaskOut) + { + return _kand_mask64(out, mask); + } + else + { + return out; + } + + // warning: no return statement in function returning non-void [-Wreturn-type] + #if defined(GAL_PROMETHEUS_COMPILER_GNU) + GAL_PROMETHEUS_ERROR_UNREACHABLE(); + #endif + }(); + + const auto mask_pattern_1 = _kshiftli_mask64(mask_byte_234, 1); + const auto mask_patten_2 = _kshiftli_mask64(mask_byte_34, 2); + if (mask_byte_4 == 0) + { + // expected continuation bytes + const auto mask_combing = _kor_mask64(mask_pattern_1, mask_patten_2); + const auto mask_byte_1234 = _kor_mask64(mask_byte_1, mask_byte_234); + + if constexpr (not Correct) + { + // mismatched continuation bytes + if constexpr (MaskOut) + { + if (mask_combing != _kxor_mask64(mask, mask_byte_1234)) + { + return false; + } + } + else + { + // XNOR of mask_combing and mask_byte_1234 should be all zero if they differ the presence of a 1 bit indicates that they overlap. + if (const auto v = _kxnor_mask64(mask_combing, mask_byte_1234); + not _kortestz_mask64_u8(v, v)) + { + return false; + } + } + } + + // identifying the last bytes of each sequence to be decoded + const auto mend = [=]() noexcept -> auto + { + if constexpr (const auto out = _kshiftri_mask64(mask_byte_1234, 1); + MaskOut) + { + return _kor_mask64(out, __mmask64{1} << (length - 1)); + } + else + { + return out; + } + }(); + + const auto last_and_third = _mm512_maskz_compress_epi8(mend, mask_identity); + const auto last_and_third_u16 = _mm512_cvtepu8_epi16(_mm512_castsi512_si256(last_and_third)); + // ASCII: 00000000 other: 11000000 + const auto non_ascii_tags = _mm512_maskz_mov_epi8(mask_not_ascii, v_c0c0_c0c0); + // high two bits cleared where not ASCII + const auto cleared_bytes = _mm512_andnot_si512(non_ascii_tags, data); + // bytes that precede non-ASCII bytes + const auto mask_before_non_ascii = _kshiftri_mask64(mask_not_ascii, 1); + const auto before_ascii_bytes = _mm512_maskz_mov_epi8(mask_before_non_ascii, cleared_bytes); + // the last byte of each character + const auto last_bytes = _mm512_maskz_permutexvar_epi8(0x5555'5555'5555'5555, last_and_third_u16, cleared_bytes); + + // indices of the second last bytes + const auto index_of_second_last_bytes = _mm512_add_epi16(v_ffff_ffff, last_and_third_u16); + // shifted into position + const auto second_last_bytes = _mm512_slli_epi16( + // the second last bytes (of two, three byte seq, surrogates) + _mm512_maskz_permutexvar_epi8(0x5555'5555'5555'5555, index_of_second_last_bytes, before_ascii_bytes), + 6 + ); + + // indices of the third last bytes + const auto index_of_third_last_bytes = _mm512_add_epi16(v_ffff_ffff, index_of_second_last_bytes); + // shifted into position + const auto third_last_bytes = _mm512_slli_epi16( + // the third last bytes (of three byte sequences, high surrogate) + _mm512_maskz_permutexvar_epi8( + 0x5555'5555'5555'5555, + index_of_third_last_bytes, + // only those that are the third last byte of a sequence + _mm512_maskz_mov_epi8(mask_byte_34, cleared_bytes) + ), + 12 + ); + + // the elements of out excluding the last element if it happens to be a high surrogate + const auto out = _mm512_ternarylogic_epi32(last_bytes, second_last_bytes, third_last_bytes, 254); + const auto native_out = utf8::to_native_utf16(out); + + if constexpr (not Correct) + { + // Encodings out of range + + // the location of 3-byte sequence start bytes in the input code units in out corresponding to 3-byte sequences. + const auto m3 = static_cast<__mmask32>(_pext_u64((mask_byte_34 & (mask ^ mask_byte_4)) << 2, mend)); + const auto mask_out_less_than_0x800 = _mm512_mask_cmplt_epu16_mask(m3, out, v_0800_0800); + const auto mask_out_minus_0x800 = _mm512_sub_epi16(out, v_d800_d800); + const auto mask_out_too_small = _mm512_mask_cmplt_epu16_mask(m3, mask_out_minus_0x800, v_0800_0800); + if (_kor_mask32(mask_out_less_than_0x800, mask_out_too_small) != 0) + { + return false; + } + } + + // we adjust mend at the end of the output. + const auto mask_processed = [=]() noexcept -> __mmask64 + { + if constexpr (MaskOut) + { + return _pdep_u64(0xffff'ffff, _kand_mask64(mend, mask)); + } + else + { + return _pdep_u64(0xffff'ffff, mend); + } + + // warning: no return statement in function returning non-void [-Wreturn-type] + #if defined(GAL_PROMETHEUS_COMPILER_GNU) + GAL_PROMETHEUS_ERROR_UNREACHABLE(); + #endif + }(); + + const auto num_out = std::popcount(mask_processed); + const auto out_mask = _bzhi_u32(~static_cast(0), static_cast(num_out)); + + _mm512_mask_storeu_epi16(it_output_current, out_mask, native_out); + + it_input_current += 64 - std::countl_zero(mask_processed); + it_output_current += num_out; + + return true; + } + + // We have a 4-byte sequence, this is the general case. + const auto mask_pattern_3 = _kshiftli_mask64(mask_byte_4, 3); + // expected continuation bytes + const auto mask_combing = _kor_mask64(_kor_mask64(mask_pattern_1, mask_patten_2), mask_pattern_3); + const auto mask_byte_1234 = _kor_mask64(mask_byte_1, mask_byte_234); + + // identifying the last bytes of each sequence to be decoded + const auto mend = [=]() noexcept -> __mmask64 + { + if constexpr (const auto out = _kor_mask64(_kshiftri_mask64(_kor_mask64(mask_pattern_3, mask_byte_1234), 1), mask_pattern_3); + MaskOut) + { + return _kor_mask64(out, __mmask64{1} << (length - 1)); + } + else + { + return out; + } + + // warning: no return statement in function returning non-void [-Wreturn-type] + #if defined(GAL_PROMETHEUS_COMPILER_GNU) + GAL_PROMETHEUS_ERROR_UNREACHABLE(); + #endif + }(); + + const auto last_and_third = _mm512_maskz_compress_epi8(mend, mask_identity); + const auto last_and_third_u16 = _mm512_cvtepu8_epi16(_mm512_castsi512_si256(last_and_third)); + // ASCII: 00000000 other: 11000000 + const auto non_ascii_tags = _mm512_maskz_mov_epi8(mask_not_ascii, v_c0c0_c0c0); + // high two bits cleared where not ASCII + const auto cleared_bytes = _mm512_andnot_si512(non_ascii_tags, data); + // bytes that precede non-ASCII bytes + const auto mask_before_non_ascii = _kshiftri_mask64(mask_not_ascii, 1); + const auto before_ascii_bytes = _mm512_maskz_mov_epi8(mask_before_non_ascii, cleared_bytes); + // the last byte of each character + const auto last_bytes = _mm512_maskz_permutexvar_epi8(0x5555'5555'5555'5555, last_and_third_u16, cleared_bytes); + + // indices of the second last bytes + const auto index_of_second_last_bytes = _mm512_add_epi16(v_ffff_ffff, last_and_third_u16); + // shifted into position + const auto second_last_bytes = _mm512_slli_epi16( + // the second last bytes (of two, three byte seq, surrogates) + _mm512_maskz_permutexvar_epi8(0x5555'5555'5555'5555, index_of_second_last_bytes, before_ascii_bytes), + 6 + ); + + // indices of the third last bytes + const auto index_of_third_last_bytes = _mm512_add_epi16(v_ffff_ffff, index_of_second_last_bytes); + // shifted into position + const auto third_last_bytes = _mm512_slli_epi16( + // the third last bytes (of three byte sequences, high surrogate) + _mm512_maskz_permutexvar_epi8( + 0x5555'5555'5555'5555, + index_of_third_last_bytes, + // only those that are the third last byte of a sequence + _mm512_maskz_mov_epi8(mask_byte_34, cleared_bytes) + ), + 12 + ); + + const auto third_second_and_last_bytes = _mm512_ternarylogic_epi32(last_bytes, second_last_bytes, third_last_bytes, 254); + const auto mask_mp3_64 = _pext_u64(mask_pattern_3, mend); + const auto mask_mp3_low = static_cast<__mmask32>(mask_mp3_64); + const auto mask_mp3_high = static_cast<__mmask32>(mask_mp3_64 >> 1); + // low surrogate: 1101110000000000, other: 0000000000000000 + const auto mask_low_surrogate = _mm512_maskz_mov_epi16(mask_mp3_low, v_dc00_dc00); + const auto tagged_low_surrogate = _mm512_or_si512(third_second_and_last_bytes, mask_low_surrogate); + const auto shifted4_third_second_and_last_bytes = _mm512_srli_epi16(third_second_and_last_bytes, 4); + + // the elements of out excluding the last element if it happens to be a high surrogate + const auto out = _mm512_mask_add_epi16( + tagged_low_surrogate, + mask_mp3_high, + shifted4_third_second_and_last_bytes, + v_d7c0_d7c0 + ); + const auto native_out = utf8::to_native_utf16(out); + + if constexpr (not Correct) + { + // mismatched continuation bytes + if constexpr (MaskOut) + { + if (mask_combing != _kxor_mask64(mask, mask_byte_1234)) + { + return false; + } + } + else + { + // XNOR of mask_combing and mask_byte_1234 should be all zero if they differ the presence of a 1 bit indicates that they overlap. + if (const auto v = _kxnor_mask64(mask_combing, mask_byte_1234); + not _kortestz_mask64_u8(v, v)) + { + return false; + } + } + + // Encodings out of range + // the location of 3-byte sequence start bytes in the input code units in out corresponding to 3-byte sequences. + const auto m3 = static_cast<__mmask32>(_pext_u64(mask_byte_34 & (mask ^ mask_byte_4) << 2, mend)); + const auto mask_out_less_than_0x800 = _mm512_mask_cmplt_epu16_mask(m3, out, v_0800_0800); + const auto mask_out_minus_0x800 = _mm512_sub_epi16(out, v_d800_d800); + const auto mask_out_too_small = _mm512_mask_cmplt_epu16_mask(m3, mask_out_minus_0x800, v_0800_0800); + const auto mask_out_greater_equal_0x400 = _mm512_mask_cmpge_epu16_mask(mask_mp3_high, mask_out_minus_0x800, v_0400_0400); + if (_kortestz_mask32_u8(mask_out_greater_equal_0x400, _kor_mask32(mask_out_less_than_0x800, mask_out_too_small)) != 0) + { + return false; + } + } + + // we adjust mend at the end of the output. + const auto mask_processed = [=, m = ~(mask_mp3_high & 0x8000'0000)]() noexcept -> __mmask64 + { + if constexpr (MaskOut) + { + return _pdep_u64(m, _kand_mask64(mend, mask)); + } + else + { + return _pdep_u64(m, mend); + } + + // warning: no return statement in function returning non-void [-Wreturn-type] + #if defined(GAL_PROMETHEUS_COMPILER_GNU) + GAL_PROMETHEUS_ERROR_UNREACHABLE(); + #endif + }(); + + const auto num_out = std::popcount(mask_processed); + const auto out_mask = _bzhi_u32(~static_cast(0), static_cast(num_out)); + + _mm512_mask_storeu_epi16(it_output_current, out_mask, native_out); + + it_input_current += 64 - std::countl_zero(mask_processed); + it_output_current += num_out; + + return true; + } + + // all ASCII or 2 byte + const auto continuation_or_ascii = [=]() noexcept -> __mmask64 + { + if constexpr (MaskOut) + { + return _kand_mask64(_knot_mask64(mask_byte_234), mask); + } + else + { + return _knot_mask64(mask_byte_234); + } + + // warning: no return statement in function returning non-void [-Wreturn-type] + #if defined(GAL_PROMETHEUS_COMPILER_GNU) + GAL_PROMETHEUS_ERROR_UNREACHABLE(); + #endif + }(); + + // on top of -0xc0 we subtract -2 which we get back later of the continuation byte tags + const auto leading_two_bytes = _mm512_maskz_sub_epi8(mask_byte_234, data, v_c2c2_c2c2); + const auto leading_mask = [=]() noexcept -> auto + { + if constexpr (MaskOut) + { + return _kand_mask64(_kor_mask64(mask_byte_1, mask_byte_234), mask); + } + else + { + return _kor_mask64(mask_byte_1, mask_byte_234); + } + }(); + + if constexpr (not Correct) + { + if constexpr (MaskOut) + { + if (_kshiftli_mask64(mask_byte_234, 1) != _kxor_mask64(mask, leading_mask)) + { + return false; + } + } + else + { + if (const auto v = _kxnor_mask64(_kshiftli_mask64(mask_byte_234, 1), leading_mask); + not _kortestz_mask64_u8(v, v)) + { + return false; + } + } + } + + if constexpr (MaskOut) + { + it_input_current += 64 - std::countl_zero(_pdep_u64(0xffff'ffff, continuation_or_ascii)); + } + else + { + // In the two-byte/ASCII scenario, we are easily latency bound, + // so we want to increment the input buffer as quickly as possible. + // We process 32 bytes unless the byte at index 32 is a continuation byte, + // in which case we include it as well for a total of 33 bytes. + // Note that if x is an ASCII byte, then the following is false: + // int8_t(x) <= int8_t(0xc0) under two's complement. + it_input_current += 32; + if (static_cast(*it_input_current) <= static_cast(0xc0)) + { + it_input_current += 1; + } + } + + const auto out = [&]() noexcept -> auto + { + // contain zero for ascii, and the data + auto lead = _mm512_maskz_compress_epi8(leading_mask, leading_two_bytes); + // zero extended into code units + lead = _mm512_cvtepu8_epi16(_mm512_castsi512_si256(lead)); + // shifted into position + lead = _mm512_slli_epi16(lead, 6); + + // the last bytes of each sequence + auto follow = _mm512_maskz_compress_epi8(continuation_or_ascii, data); + // zero extended into code units + follow = _mm512_cvtepu8_epi16(_mm512_castsi512_si256(follow)); + + // combining lead and follow + const auto final = _mm512_add_epi16(follow, lead); + const auto native_final = utf8::to_native_utf16(final); + + return native_final; + }(); + + if constexpr (MaskOut) + { + const auto num_out = std::popcount(_pdep_u64(0xffff'ffff, leading_mask)); + const auto out_mask = _bzhi_u32(~static_cast(0), static_cast(num_out)); + + _mm512_mask_storeu_epi16(it_output_current, out_mask, out); + + it_output_current += num_out; + } + else + { + const auto num_out = std::popcount(leading_mask); + const auto out_mask = _bzhi_u32(~static_cast(0), static_cast(num_out)); + + _mm512_mask_storeu_epi16(it_output_current, out_mask, out); + + it_output_current += num_out; + } + + return true; + } + }; + const auto do_fallback = [&]() noexcept -> result_error_input_output_type + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + if (current_input_length >= advance) + { + // We must check whether we are the fourth continuation byte + const auto c1 = static_cast(*it_input_current - 0); + const auto c2 = static_cast(*it_input_current - 1); + const auto c3 = static_cast(*it_input_current - 2); + const auto c4 = static_cast(*it_input_current - 3); + + if ( + (c1 & 0xc0) == 0x80 and + (c2 & 0xc0) == 0x80 and + (c3 & 0xc0) == 0x80 and + (c4 & 0xc0) == 0x80 + ) + { + return {.error = ErrorCode::TOO_LONG, .input = current_input_length, .output = current_output_length}; + } + } + + const auto result = [=, remaining = static_cast(it_input_end - it_input_current)]() noexcept + { + if constexpr (InputType == CharsType::UTF8_CHAR) + { + if constexpr (OutputType == CharsType::UTF16_LE) + { + return utf8_char::scalar::rewind_and_write_utf16_le(it_output_current, it_input_begin, {it_input_current, remaining}); + } + else + { + return utf8_char::scalar::rewind_and_write_utf16_be(it_output_current, it_input_begin, {it_input_current, remaining}); + } + } + else + { + if constexpr (OutputType == CharsType::UTF16_LE) + { + return chars::utf8::scalar::rewind_and_write_utf16_le(it_output_current, it_input_begin, {it_input_current, remaining}); + } + else + { + return chars::utf8::scalar::rewind_and_write_utf16_be(it_output_current, it_input_begin, {it_input_current, remaining}); + } + } + }(); + return {.error = result.error, .input = result.input + current_input_length, .output = result.output + current_output_length}; + }; + + while (it_input_current + advance <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance}; + #endif + + const auto success = do_process.template operator()(); + + if constexpr (Pure or Correct) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(success); + } + else + { + if (not success) + { + return do_fallback(); + } + } + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto success = do_process.template operator()(); + + if constexpr (Pure or Correct) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(success); + } + else + { + if (not success) + { + return do_fallback(); + } + } + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) and + ( + OutputType == CharsType::UTF32 + ) + [[nodiscard]] constexpr auto write_utf32( + const typename output_type_of::pointer output, + const input_type_of input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using input_type = input_type_of; + using pointer_type = typename input_type::const_pointer; + using size_type = typename input_type::size_type; + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + constexpr auto advance = 4 * advance_of_utf32; + constexpr auto invalid_count = std::numeric_limits::max(); + + const auto do_fallback = [&]() noexcept -> result_error_input_output_type + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + if (current_input_length >= advance) + { + const auto c1 = static_cast(*it_input_current + 0); + + if ((c1 & 0xc0) != 0x80) + { + // We might have an error that occurs right before `it_input_current`. + // This is only a concern if `c1` is not a continuation byte. + it_input_current -= 1; + } + else + { + // We must check whether we are the fourth continuation byte. + const auto c2 = static_cast(*it_input_current - 1); + const auto c3 = static_cast(*it_input_current - 2); + const auto c4 = static_cast(*it_input_current - 3); + + if ( + (c2 & 0xc0) == 0x80 and + (c3 & 0xc0) == 0x80 and + (c4 & 0xc0) == 0x80 + ) + { + return {.error = ErrorCode::TOO_LONG, .input = current_input_length, .output = current_output_length}; + } + } + } + + const auto remaining = static_cast(it_input_end - it_input_current); + + const auto result = [=]() noexcept + { + if constexpr (InputType == CharsType::UTF8_CHAR) + { + return utf8_char::scalar::rewind_and_write_utf32(it_output_current, it_input_begin, {it_input_current, remaining}); + } + else + { + return chars::utf8::scalar::rewind_and_write_utf32(it_output_current, it_input_begin, {it_input_current, remaining}); + } + }(); + return {.error = result.error, .input = result.input + current_input_length, .output = result.output + current_output_length}; + }; + + detail::utf8::icelake::avx512_utf8_checker checker{}; + + // In the main loop, we consume 64 bytes per iteration, but we access 64 + 4 bytes. + while (it_input_current + advance + sizeof(std::uint32_t) <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance + sizeof(std::uint32_t)}; + #endif + + const auto data = _mm512_loadu_si512(it_input_current); + if (checker.check_data(data)) + { + detail::utf8::icelake::write_utf32_pure(it_output_current, data); + + it_input_current += advance; + continue; + } + + if (checker.has_error()) + { + return do_fallback(); + } + + const auto lane_0 = detail::utf8::icelake::broadcast<0>(data); + const auto lane_1 = detail::utf8::icelake::broadcast<1>(data); + const auto lane_2 = detail::utf8::icelake::broadcast<2>(data); + const auto lane_3 = detail::utf8::icelake::broadcast<3>(data); + const auto lane_4 = _mm512_set1_epi32(static_cast(memory::unaligned_load(it_input_current + advance))); + + auto valid_count_0 = invalid_count; + auto vec_0 = detail::utf8::icelake::expand_and_identify(lane_0, lane_1, valid_count_0); + GAL_PROMETHEUS_ERROR_ASSUME(valid_count_0 != invalid_count); + auto valid_count_1 = invalid_count; + auto vec_1 = detail::utf8::icelake::expand_and_identify(lane_1, lane_2, valid_count_1); // NOLINT(readability-suspicious-call-argument) + GAL_PROMETHEUS_ERROR_ASSUME(valid_count_1 != invalid_count); + + if (valid_count_0 + valid_count_1 <= advance_of_utf32) + { + const auto mask_0 = _bzhi_u32(~static_cast(0), static_cast(valid_count_1)); + const auto mask_1 = static_cast<__mmask16>(mask_0 << valid_count_0); + + const auto expanded = _mm512_mask_expand_epi32(vec_0, mask_1, vec_1); + vec_0 = detail::utf8::icelake::expand_to_utf32(expanded); + valid_count_0 += valid_count_1; + + detail::utf8::icelake::write_utf32(it_output_current, vec_0, valid_count_0); + } + else + { + vec_0 = detail::utf8::icelake::expand_to_utf32(vec_0); + vec_1 = detail::utf8::icelake::expand_to_utf32(vec_1); + + detail::utf8::icelake::write_utf32(it_output_current, vec_0, valid_count_0); + detail::utf8::icelake::write_utf32(it_output_current, vec_1, valid_count_1); + } + + auto valid_count_2 = invalid_count; + auto vec_2 = detail::utf8::icelake::expand_and_identify(lane_2, lane_3, valid_count_2); + GAL_PROMETHEUS_ERROR_ASSUME(valid_count_2 != invalid_count); + auto valid_count_3 = invalid_count; + auto vec_3 = detail::utf8::icelake::expand_and_identify(lane_3, lane_4, valid_count_3); + GAL_PROMETHEUS_ERROR_ASSUME(valid_count_3 != invalid_count); + + if (valid_count_2 + valid_count_3 <= advance_of_utf32) + { + const auto mask_0 = _bzhi_u32(~static_cast(0), static_cast(valid_count_3)); + const auto mask_1 = static_cast<__mmask16>(mask_0 << valid_count_2); + + const auto expanded = _mm512_mask_expand_epi32(vec_2, mask_1, vec_1); + vec_2 = detail::utf8::icelake::expand_to_utf32(expanded); + valid_count_2 += valid_count_3; + + detail::utf8::icelake::write_utf32(it_output_current, vec_2, valid_count_2); + } + else + { + vec_2 = detail::utf8::icelake::expand_to_utf32(vec_2); + vec_3 = detail::utf8::icelake::expand_to_utf32(vec_3); + + detail::utf8::icelake::write_utf32(it_output_current, vec_2, valid_count_2); + detail::utf8::icelake::write_utf32(it_output_current, vec_3, valid_count_3); + } + + it_input_current += advance; + } + + auto it_valid_input_current = it_input_current; + + // For the final pass, we validate 64 bytes, + // but we only transcode 3*16 bytes, + // so we may end up double-validating 16 bytes. + if (it_input_current + advance <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance}; + #endif + + if (const auto data = _mm512_loadu_si512(it_input_current); + checker.check_data(data)) + { + detail::utf8::icelake::write_utf32_pure(it_output_current, data); + + it_input_current += advance; + } + else if (checker.has_error()) + { + return do_fallback(); + } + else + { + // 64 + const auto lane_0 = detail::utf8::icelake::broadcast<0>(data); + const auto lane_1 = detail::utf8::icelake::broadcast<1>(data); + const auto lane_2 = detail::utf8::icelake::broadcast<2>(data); + const auto lane_3 = detail::utf8::icelake::broadcast<3>(data); + + auto valid_count_0 = invalid_count; + auto vec_0 = detail::utf8::icelake::expand_and_identify(lane_0, lane_1, valid_count_0); + GAL_PROMETHEUS_ERROR_ASSUME(valid_count_0 != invalid_count); + auto valid_count_1 = invalid_count; + auto vec_1 = detail::utf8::icelake::expand_and_identify(lane_1, lane_2, valid_count_1); // NOLINT(readability-suspicious-call-argument) + GAL_PROMETHEUS_ERROR_ASSUME(valid_count_1 != invalid_count); + + if (valid_count_0 + valid_count_1 <= advance_of_utf32) + { + const auto mask_0 = _bzhi_u32(~static_cast(0), static_cast(valid_count_1)); + const auto mask_1 = static_cast<__mmask16>(mask_0 << valid_count_0); + + const auto expanded = _mm512_mask_expand_epi32(vec_0, mask_1, vec_1); + vec_0 = detail::utf8::icelake::expand_to_utf32(expanded); + valid_count_0 += valid_count_1; + + detail::utf8::icelake::write_utf32(it_output_current, vec_0, valid_count_0); + } + else + { + vec_0 = detail::utf8::icelake::expand_to_utf32(vec_0); + vec_1 = detail::utf8::icelake::expand_to_utf32(vec_1); + + detail::utf8::icelake::write_utf32(it_output_current, vec_0, valid_count_0); + detail::utf8::icelake::write_utf32(it_output_current, vec_1, valid_count_1); + } + + detail::utf8::icelake::transcode_16(it_output_current, lane_2, lane_3); + + it_input_current += 3 * advance_of_utf32; + } + + it_valid_input_current += advance; + } + + { + const auto remaining = it_input_end - it_valid_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto mask = _bzhi_u64(~static_cast(0), static_cast(remaining)); + const auto data = _mm512_maskz_loadu_epi8(mask, it_valid_input_current); + + std::ignore = checker.check_data(data); + } + checker.check_eof(); + + if (checker.has_error()) + { + return do_fallback(); + } + } + { + // the AVX512 procedure looks up 4 bytes forward, + // and correctly converts multibyte chars even if their continuation bytes lie outside 16-byte window. + // It means, we have to skip continuation bytes from the beginning `it_input_current`, as they were already consumed. + it_input_current = std::ranges::find_if( + it_input_current, + it_input_end, + [](const auto c) noexcept -> bool + { + return (static_cast(c) & 0xc0) != 0x80; + } + ); + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance); + + if (remaining != 0) + { + const auto result = Scalar::convert(it_output_current, {it_input_current, static_cast(remaining)}); + + if constexpr (Correct) + { + it_input_current += remaining; + it_output_current += result.output; + } + else if constexpr (Pure) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not result.has_error()); + it_input_current += result.input; + it_output_current += result.input; + } + else + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not result.has_error()); + it_input_current += result.input; + it_output_current += result.output; + } + + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + if constexpr (Correct) + { + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + else + { + return {.error = result.error, .input = current_input_length, .output = current_output_length}; + } + } + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + // UTF8_CHAR => UTF8 + // UTF8 => UTF8_CHAR + template + requires ( + InputType == CharsType::UTF8_CHAR and + OutputType == CharsType::UTF8 + ) or + ( + InputType == CharsType::UTF8 and + OutputType == CharsType::UTF8_CHAR + ) + [[nodiscard]] constexpr auto transform( + const typename output_type_of::pointer output, + const input_type_of input + ) noexcept -> result_error_input_type + { + using char_type = typename input_type_of::value_type; + + if (const auto result = icelake::validate(input); + result.has_error()) + { + std::memcpy(output, input.data(), result.input * sizeof(char_type)); + return {.error = result.error, .input = result.input}; + } + + std::memcpy(output, input.data(), input.size() * sizeof(char_type)); + return {.error = ErrorCode::NONE, .input = input.size()}; + } + } + } + + namespace utf16 + { + using input_type = chars::utf16::input_type; + // using char_type = chars::utf16::char_type; + using size_type = chars::utf16::size_type; + using pointer_type = chars::utf16::pointer_type; + + constexpr auto advance_of_latin = sizeof(data_type) / sizeof(input_type_of::value_type); + constexpr auto advance_of_utf8 = sizeof(data_type) / sizeof(input_type_of::value_type); + constexpr auto advance_of_utf16 = sizeof(data_type) / sizeof(input_type_of::value_type); + constexpr auto advance_of_utf32 = sizeof(data_type) / sizeof(input_type_of::value_type); + + template + requires ( + InputType == CharsType::UTF16_LE or + InputType == CharsType::UTF16_BE or + InputType == CharsType::UTF16 + ) + [[nodiscard]] constexpr auto read_native(const pointer_type source) noexcept -> data_type + { + const auto data = _mm512_loadu_si512(source); + + if constexpr (common::not_native_endian()) + { + const auto byte_flip = _mm512_setr_epi64( + // clang-format off + 0x0607'0405'0203'0001, 0x0e0f'0c0d'0a0b'0809, + 0x0607'0405'0203'0001, 0x0e0f'0c0d'0a0b'0809, + 0x0607'0405'0203'0001, 0x0e0f'0c0d'0a0b'0809, + 0x0607'0405'0203'0001, 0x0e0f'0c0d'0a0b'0809 + // clang-format on + ); + + return _mm512_shuffle_epi8(data, byte_flip); + } + else + { + return data; + } + } + + template + requires ( + InputType == CharsType::UTF16_LE or + InputType == CharsType::UTF16_BE or + InputType == CharsType::UTF16 + ) + [[nodiscard]] constexpr auto read_native(const pointer_type source, const std::size_t length) noexcept -> data_type + { + const auto mask = _bzhi_u32(~static_cast(0), static_cast(length)); + const auto data = _mm512_maskz_loadu_epi16(mask, source); + + if constexpr (common::not_native_endian()) + { + const auto byte_flip = _mm512_setr_epi64( + // clang-format off + 0x0607'0405'0203'0001, 0x0e0f'0c0d'0a0b'0809, + 0x0607'0405'0203'0001, 0x0e0f'0c0d'0a0b'0809, + 0x0607'0405'0203'0001, 0x0e0f'0c0d'0a0b'0809, + 0x0607'0405'0203'0001, 0x0e0f'0c0d'0a0b'0809 + // clang-format on + ); + + return _mm512_shuffle_epi8(data, byte_flip); + } + else + { + return data; + } + } + + namespace icelake + { + template + requires ( + InputType == CharsType::UTF16_LE or + InputType == CharsType::UTF16_BE + ) + [[nodiscard]] constexpr auto validate(const input_type input) noexcept -> result_error_input_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + // keep an overlap of one code unit + constexpr auto advance_keep_high_surrogate = advance_of_utf16 - 1; + + const auto v_d800 = _mm512_set1_epi16(static_cast(0xd800)); + const auto v_0800 = _mm512_set1_epi16(0x0800); + const auto v_0400 = _mm512_set1_epi16(0x0400); + + while (it_input_current + advance_of_utf16 <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_utf16}; + #endif + + const auto data = utf16::read_native(it_input_current); + const auto diff = _mm512_sub_epi16(data, v_d800); + + if (const auto surrogates = _mm512_cmplt_epu16_mask(diff, v_0800); + surrogates) + { + const auto high_surrogates = _mm512_cmplt_epu16_mask(diff, v_0400); + const auto low_surrogates = surrogates ^ high_surrogates; + // high must be followed by low + if ((high_surrogates << 1) != low_surrogates) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + + const auto extra_high = std::countr_zero(static_cast(high_surrogates & ~(low_surrogates >> 1))); + const auto extra_low = std::countr_zero(static_cast(low_surrogates & ~(high_surrogates << 1))); + + return {.error = ErrorCode::SURROGATE, .input = current_input_length + std::ranges::min(extra_high, extra_low)}; + } + + if (const auto ends_with_high = ((high_surrogates & 0x8000'0000) != 0); + ends_with_high) + { + // advance only by 31 code units so that we start with the high surrogate on the next round. + it_input_current += advance_keep_high_surrogate; + } + else + { + it_input_current += advance_of_utf16; + } + } + + it_input_current += advance_of_utf16; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_utf16); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto data = utf16::read_native(it_input_current, static_cast(remaining)); + const auto diff = _mm512_sub_epi16(data, v_d800); + + if (const auto surrogates = _mm512_cmplt_epu16_mask(diff, v_0800); + surrogates) + { + const auto high_surrogates = _mm512_cmplt_epu16_mask(diff, v_0400); + const auto low_surrogates = surrogates ^ high_surrogates; + // high must be followed by low + if ((high_surrogates << 1) != low_surrogates) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + + const auto extra_high = std::countr_zero(static_cast(high_surrogates & ~(low_surrogates >> 1))); + const auto extra_low = std::countr_zero(static_cast(low_surrogates & ~(high_surrogates << 1))); + + return {.error = ErrorCode::SURROGATE, .input = current_input_length + std::ranges::min(extra_high, extra_low)}; + } + } + + it_input_current += remaining; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + return {.error = ErrorCode::NONE, .input = current_input_length}; + } + + template + requires ( + InputType == CharsType::UTF16_LE or + InputType == CharsType::UTF16_BE + ) + [[nodiscard]] constexpr auto length(const input_type input) noexcept -> size_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + // ReSharper disable CppClangTidyBugproneBranchClone + if constexpr (OutputType == CharsType::LATIN) + { + return input.size(); + } + // ReSharper restore CppClangTidyBugproneBranchClone + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + // ReSharper disable CppInconsistentNaming + // ReSharper disable IdentifierTypo + + const auto v_007f = _mm512_set1_epi16(0x007f); + const auto v_07ff = _mm512_set1_epi16(0x07ff); + const auto v_dfff = _mm512_set1_epi16(static_cast(0xdfff)); + const auto v_d800 = _mm512_set1_epi16(static_cast(0xd800)); + + // ReSharper restore IdentifierTypo + // ReSharper restore CppInconsistentNaming + + auto result_length = static_cast(0); + while (it_input_current + advance_of_utf8 <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_utf8}; + #endif + + const auto data = utf16::read_native(it_input_current); + + const auto ascii_bitmask = _mm512_cmple_epu16_mask(data, v_007f); + const auto two_bytes_bitmask = _mm512_mask_cmple_epu16_mask(~ascii_bitmask, data, v_07ff); + const auto surrogates_bitmask = + _mm512_mask_cmple_epu16_mask(~(ascii_bitmask | two_bytes_bitmask), data, v_dfff) & + _mm512_mask_cmpge_epu16_mask(~(ascii_bitmask | two_bytes_bitmask), data, v_d800); + + const auto ascii_count = std::popcount(ascii_bitmask); + const auto two_bytes_count = std::popcount(two_bytes_bitmask); + const auto surrogates_bytes_count = std::popcount(surrogates_bitmask); + const auto three_bytes_count = advance_of_utf8 - ascii_count - two_bytes_count - surrogates_bytes_count; + + result_length += + 1 * ascii_count + + 2 * two_bytes_count + + 2 * surrogates_bytes_count + + 3 * three_bytes_count; + it_input_current += advance_of_utf8; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_utf8); + + if (remaining != 0) + { + result_length += Scalar::length({it_input_current, static_cast(remaining)}); + } + + return result_length; + } + // ReSharper disable CppClangTidyBugproneBranchClone + else if constexpr (OutputType == CharsType::UTF16_LE or OutputType == CharsType::UTF16_BE or OutputType == CharsType::UTF16) + { + return input.size(); + } + // ReSharper restore CppClangTidyBugproneBranchClone + else if constexpr (OutputType == CharsType::UTF32) + { + const auto low = _mm512_set1_epi16(static_cast(0xdc00)); + const auto high = _mm512_set1_epi16(static_cast(0xdfff)); + + auto result_length = static_cast(0); + while (it_input_current + advance_of_utf32 <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_utf32}; + #endif + + const auto data = utf16::read_native(it_input_current); + + const auto not_high_surrogate_bitmask = + _mm512_cmpgt_epu16_mask(data, high) | + _mm512_cmplt_epu16_mask(data, low); + + const auto not_high_surrogate_count = std::popcount(not_high_surrogate_bitmask); + + result_length += not_high_surrogate_count; + it_input_current += advance_of_utf32; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_utf32); + + if (remaining != 0) + { + result_length += Scalar::length({it_input_current, static_cast(remaining)}); + } + + return result_length; + } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template + requires ( + InputType == CharsType::UTF16_LE or + InputType == CharsType::UTF16_BE + ) and + ( + OutputType == CharsType::LATIN + ) + [[nodiscard]] constexpr auto write_latin( + const typename output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + // ReSharper disable CppInconsistentNaming + // ReSharper disable IdentifierTypo + + const auto v_00ff = _mm512_set1_epi16(0x00ff); + + // ReSharper restore IdentifierTypo + // ReSharper restore CppInconsistentNaming + + const auto do_write = [&](const data_type data, const std::size_t data_length) noexcept -> void + { + if constexpr (not MaskOut) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data_length == advance_of_latin); + } + + const auto shuffle_mask = _mm512_set_epi8( + // clang-format off + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 62, 60, 58, 56, 54, 52, 50, 48, 46, 44, 42, 40, 38, 36, 34, 32, + 30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10, 8u, 06, 04, 02, 00 + // clang-format on + ); + + if constexpr (const auto out = _mm512_castsi512_si256(_mm512_permutexvar_epi8(shuffle_mask, data)); + MaskOut) + { + const auto source_mask = _bzhi_u32(~static_cast(0), static_cast(data_length)); + + _mm256_mask_storeu_epi8(it_output_current, source_mask, out); + } + else + { + auto* p = GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m256i*, it_output_current); + + _mm256_storeu_si256(p, out); + } + }; + + while (it_input_current + advance_of_latin <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_latin}; + #endif + + const auto data = utf16::read_native(it_input_current); + + if constexpr (not Pure or not Correct) + { + if (const auto mask = _mm512_cmpgt_epu16_mask(data, v_00ff); + mask) + { + const auto extra = static_cast(std::countr_zero(mask)); + const auto result = Scalar::convert(it_output_current, {it_input_current, extra}); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(result.output == extra); + + it_input_current += extra; + it_output_current += result.output; + + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + return {.error = ErrorCode::TOO_LARGE, .input = current_input_length, .output = current_output_length}; + } + } + + do_write.template operator()(data, advance_of_latin); + + it_input_current += advance_of_latin; + it_output_current += advance_of_latin; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_latin); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto data = utf16::read_native(it_input_current, static_cast(remaining)); + + if constexpr (not Pure or not Correct) + { + if (const auto mask = _mm512_cmpgt_epu16_mask(data, v_00ff); + mask) + { + const auto extra = static_cast(std::countr_zero(mask)); + const auto result = Scalar::convert(it_output_current, {it_input_current, extra}); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(result.output == extra); + + it_input_current += extra; + it_output_current += result.output; + + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + return {.error = ErrorCode::TOO_LARGE, .input = current_input_length, .output = current_output_length}; + } + } + + do_write.template operator()(data, static_cast(remaining)); + + it_input_current += remaining; + it_output_current += remaining; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + template + requires ( + InputType == CharsType::UTF16_LE or + InputType == CharsType::UTF16_BE + ) and + ( + OutputType == CharsType::UTF8_CHAR or + OutputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto write_utf8( + const typename output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + // keep an overlap of one code unit + constexpr auto advance_keep_high_surrogate = advance_of_utf8 - 1; + + struct process_result + { + // 0~31 + std::uint8_t processed_input; + // processed_input + ? + std::uint8_t num_output; + bool end_with_surrogate; + + std::uint8_t pad; + }; + static_assert(sizeof(process_result) == sizeof(std::uint32_t)); + + const auto do_process = [&]( + const data_type data, + const std::size_t data_length, + const bool end_with_surrogate + ) noexcept -> process_result + { + if constexpr (Pure or Correct) + { + std::ignore = end_with_surrogate; + } + + // ReSharper disable CppInconsistentNaming + // ReSharper disable IdentifierTypo + + const auto v_0000_0080 = _mm512_set1_epi16(0x0000'0080); + const auto v_0000_3f3f = _mm512_set1_epi16(0x0000'3f3f); + const auto v_0000_ffff = _mm512_set1_epi16(static_cast(0x0000'ffff)); + const auto v_0000_0800 = _mm512_set1_epi16(0x0000'0800); + const auto v_0000_80c0 = _mm512_set1_epi16(static_cast(0x0000'80c0)); + const auto v_8080_e000 = _mm512_set1_epi32(static_cast(0x8080'e000)); + const auto v_0000_fc00 = _mm512_set1_epi16(static_cast(0x0000'fc00)); + const auto v_0000_d800 = _mm512_set1_epi16(static_cast(0x0000'd800)); + const auto v_0000_dc00 = _mm512_set1_epi16(static_cast(0x0000'dc00)); + const auto v_8080_80f0 = _mm512_set1_epi32(static_cast(0x8080'80f0)); + const auto v_fca0_2400 = _mm512_set1_epi32(static_cast(0xfca0'2400)); + const auto v_80c0_0000 = _mm512_set1_epi32(static_cast(0x80c0'0000)); + const auto v_ffff_ffff = _mm512_set1_epi32(static_cast(0xffff'ffff)); + const auto v_0001_0101 = _mm512_set1_epi32(0x0001'0101); + const auto v_3f3f_3f3f = _mm512_set1_epi32(0x3f3f'3f3f); + const auto v_2026_2c32_0006_0c12 = _mm512_set1_epi64(0x2026'2c32'0006'0c12); + + // ReSharper restore IdentifierTypo + // ReSharper restore CppInconsistentNaming + + const auto data_mask = _bzhi_u32(~static_cast(0), static_cast(data_length)); + + if constexpr (Pure) + { + _mm512_mask_cvtepi16_storeu_epi8(it_output_current, data_mask, data); + + return process_result + { + .processed_input = static_cast(data_length), + .num_output = static_cast(data_length), + .end_with_surrogate = false, + .pad = 0 + }; + } + else + { + const auto is_234_byte = _mm512_mask_cmpge_epu16_mask(data_mask, data, v_0000_0080); + if (_ktestz_mask32_u8(data_mask, is_234_byte)) + { + // ASCII only + _mm512_mask_cvtepi16_storeu_epi8(it_output_current, data_mask, data); + + return process_result + { + .processed_input = static_cast(data_length), + .num_output = static_cast(data_length), + .end_with_surrogate = false, + .pad = 0 + }; + } + + const auto is_12_byte = _mm512_cmplt_epu16_mask(data, v_0000_0800); + if (_ktestc_mask32_u8(is_12_byte, data_mask)) + { + // 1/2 byte only + + // (A|B)&C + const auto two_bytes = _mm512_ternarylogic_epi32( + _mm512_slli_epi16(data, 8), + _mm512_srli_epi16(data, 6), + v_0000_3f3f, + 0xa8 + ); + const auto compare_mask = _mm512_mask_blend_epi16(data_mask, v_0000_ffff, v_0000_0800); + const auto in = _mm512_mask_add_epi16(data, is_234_byte, two_bytes, v_0000_80c0); + const auto smoosh = _mm512_cmpge_epu8_mask(in, compare_mask); + + const auto out = _mm512_maskz_compress_epi8(smoosh, in); + const auto out_mask = _pext_u64(smoosh, smoosh); + + _mm512_mask_storeu_epi8(it_output_current, out_mask, out); + + return process_result + { + .processed_input = static_cast(data_length), + .num_output = static_cast(data_length + std::popcount(is_234_byte)), + .end_with_surrogate = false, + .pad = 0 + }; + } + + auto low = _mm512_cvtepu16_epi32(_mm512_castsi512_si256(data)); + auto high = _mm512_cvtepu16_epi32(_mm512_extracti32x8_epi32(data, 1)); + auto tag_low = v_8080_e000; + auto tag_high = v_8080_e000; + + const auto high_surrogate_mask = _mm512_mask_cmpeq_epu16_mask( + data_mask, + _mm512_and_epi32(data, v_0000_fc00), + v_0000_d800 + ); + const auto low_surrogate_mask = _mm512_cmpeq_epu16_mask( + _mm512_and_epi32(data, v_0000_fc00), + v_0000_dc00 + ); + + bool this_end_with_surrogate = false; + if (not _kortestz_mask32_u8(high_surrogate_mask, low_surrogate_mask)) + { + // handle surrogates + + const auto high_surrogate_mask_high = static_cast<__mmask16>(high_surrogate_mask >> 16); + const auto high_surrogate_mask_low = static_cast<__mmask16>(high_surrogate_mask); + + low = [low, high_surrogate_mask_low](const auto l) mutable noexcept + { + low = _mm512_mask_slli_epi32(low, high_surrogate_mask_low, low, 10); + low = _mm512_mask_add_epi32(low, high_surrogate_mask_low, low, l); + return low; + }(_mm512_add_epi32(_mm512_alignr_epi32(high, low, 1), v_fca0_2400)); + high = [high, high_surrogate_mask_high](const auto h) mutable noexcept + { + high = _mm512_mask_slli_epi32(high, high_surrogate_mask_high, high, 10); + high = _mm512_mask_add_epi32(high, high_surrogate_mask_high, high, h); + + return high; + }(_mm512_add_epi32(_mm512_alignr_epi32(low, high, 1), v_fca0_2400)); + + tag_low = _mm512_mask_mov_epi32(tag_low, high_surrogate_mask_low, v_8080_80f0); + tag_high = _mm512_mask_mov_epi32(tag_high, high_surrogate_mask_high, v_8080_80f0); + + this_end_with_surrogate = high_surrogate_mask >> 30; + + if (not Correct) + { + // check for mismatched surrogates + if (((high_surrogate_mask << 1) | +end_with_surrogate) ^ low_surrogate_mask) + { + const auto low_no_high = low_surrogate_mask & ~((high_surrogate_mask << 1) | +end_with_surrogate); + const auto high_no_low = high_surrogate_mask & ~(low_surrogate_mask >> 1); + const auto length = std::countr_zero(low_no_high | high_no_low); + + return process_result + { + .processed_input = static_cast(length), + .num_output = 0, + .end_with_surrogate = end_with_surrogate, + .pad = 0 + }; + } + } + } + + high = _mm512_maskz_mov_epi32(_cvtu32_mask16(0x0000'7fff), high); + + const auto out_mask = _kandn_mask32(low_surrogate_mask, data_mask); + const auto out_mask_high = static_cast<__mmask16>(out_mask >> 16); + const auto out_mask_low = static_cast<__mmask16>(out_mask); + + const auto magic_low = _mm512_mask_blend_epi32(out_mask_low, v_ffff_ffff, v_0001_0101); + const auto magic_high = _mm512_mask_blend_epi32(out_mask_high, v_ffff_ffff, v_0001_0101); + + const auto is_1_byte = _knot_mask32(is_234_byte); + + const auto is_1_byte_high = static_cast<__mmask16>(is_1_byte >> 16); + const auto is_1_byte_low = static_cast<__mmask16>(is_1_byte); + + const auto is_12_byte_high = static_cast<__mmask16>(is_12_byte >> 16); + const auto is_12_byte_low = static_cast<__mmask16>(is_12_byte); + + tag_low = _mm512_mask_mov_epi32(tag_low, is_12_byte_low, v_80c0_0000); + tag_high = _mm512_mask_mov_epi32(tag_high, is_12_byte_high, v_80c0_0000); + + const auto multi_shift_low = _mm512_mask_slli_epi32( + _mm512_ternarylogic_epi32( + _mm512_multishift_epi64_epi8(v_2026_2c32_0006_0c12, low), + v_3f3f_3f3f, + tag_low, + 0xea + ), + is_1_byte_low, + low, + 24 + ); + const auto multi_shift_high = _mm512_mask_slli_epi32( + _mm512_ternarylogic_epi32( + _mm512_multishift_epi64_epi8(v_2026_2c32_0006_0c12, high), + v_3f3f_3f3f, + tag_high, + 0xea + ), + is_1_byte_high, + high, + 24 + ); + + const auto want_low = _mm512_cmpge_epu8_mask(multi_shift_low, magic_low); + const auto want_high = _mm512_cmpge_epu8_mask(multi_shift_high, magic_high); + + const auto out_low = _mm512_maskz_compress_epi8(want_low, multi_shift_low); + const auto out_high = _mm512_maskz_compress_epi8(want_high, multi_shift_high); + + const auto want_low_length = std::popcount(want_low); + const auto want_high_length = std::popcount(want_high); + const auto want_low_mask = _pext_u64(want_low, want_low); + const auto want_high_mask = _pext_u64(want_high, want_high); + + _mm512_mask_storeu_epi8(it_output_current + 0, want_low_mask, out_low); + _mm512_mask_storeu_epi8(it_output_current + want_low_length, want_high_mask, out_high); + + return process_result + { + .processed_input = static_cast(data_length), + .num_output = static_cast(want_low_length + want_high_length), + .end_with_surrogate = this_end_with_surrogate, + .pad = 0 + }; + } + }; + + bool end_with_surrogate = false; + while (it_input_current + advance_of_utf8 <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_utf8}; + #endif + + const auto data = utf16::read_native(it_input_current); + + if (const auto result = do_process(data, advance_keep_high_surrogate, end_with_surrogate); + result.processed_input != advance_keep_high_surrogate) + { + // surrogate mismatch + + const auto valid_mask = _bzhi_u32(~static_cast(0), static_cast(result.processed_input)); + const auto valid_data = _mm512_maskz_mov_epi16(valid_mask, data); + const auto valid_result = do_process(valid_data, result.processed_input, end_with_surrogate); + + it_input_current += valid_result.processed_input; + it_output_current += valid_result.num_output; + + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + return {.error = ErrorCode::SURROGATE, .input = current_input_length, .output = current_output_length}; + } + else + { + it_input_current += result.processed_input; + it_output_current += result.num_output; + end_with_surrogate = result.end_with_surrogate; + } + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_utf8); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto data = utf16::read_native(it_input_current, static_cast(remaining)); + + if (const auto result = do_process(data, static_cast(remaining), end_with_surrogate); + result.processed_input != remaining) + { + // surrogate mismatch + + const auto valid_mask = _bzhi_u32(~static_cast(0), static_cast(result.processed_input)); + const auto valid_data = _mm512_maskz_mov_epi16(valid_mask, data); + const auto valid_result = do_process(valid_data, result.processed_input, end_with_surrogate); + + it_input_current += valid_result.processed_input; + it_output_current += valid_result.num_output; + + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + return {.error = ErrorCode::SURROGATE, .input = current_input_length, .output = current_output_length}; + } + else + { + it_input_current += result.processed_input; + it_output_current += result.num_output; + end_with_surrogate = result.end_with_surrogate; + } + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + template + requires ( + InputType == CharsType::UTF16_LE or + InputType == CharsType::UTF16_BE + ) and + ( + OutputType == CharsType::UTF32 + ) + [[nodiscard]] constexpr auto write_utf32( + const typename output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + // keep an overlap of one code unit + constexpr auto advance_keep_high_surrogate = advance_of_utf32 - 1; + + struct process_result + { + // 0~31 + std::uint8_t processed_input; + // processed_input + ? + std::uint8_t num_output; + // keep track if the 31st word is a high surrogate as a carry + std::uint8_t surrogate_carry; + + bool error; + }; + static_assert(sizeof(process_result) == sizeof(std::uint32_t)); + + constexpr auto data_length_full_block = std::numeric_limits::max(); + const auto do_process = [&]( + const data_type data, + const std::size_t data_length, + const bool surrogate_carry + ) noexcept -> process_result + { + if constexpr (Pure or Correct) + { + std::ignore = surrogate_carry; + } + + if constexpr (MaskOut) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data_length != data_length_full_block); + } + else + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(data_length == data_length_full_block); + } + + const auto data_mask = _bzhi_u32(~static_cast(0), static_cast(data_length)); + if constexpr (not MaskOut) + { + std::ignore = data_mask; + } + + const auto [high_surrogate_mask, low_surrogate_mask] = [data, data_mask]() noexcept + { + const auto v_0000_fc00 = _mm512_set1_epi16(static_cast(0x0000'fc00)); + const auto v_0000_d800 = _mm512_set1_epi16(static_cast(0x0000'd800)); + const auto v_0000_dc00 = _mm512_set1_epi16(static_cast(0x0000'dc00)); + + const auto v = _mm512_and_si512(data, v_0000_fc00); + const auto low = _mm512_cmpeq_epi16_mask(v, v_0000_dc00); + + if constexpr (MaskOut) + { + const auto high = _mm512_mask_cmpeq_epu16_mask(data_mask, v, v_0000_d800); + + return std::make_pair(high, low); + } + else + { + std::ignore = data_mask; + + const auto high = _mm512_cmpeq_epi16_mask(v, v_0000_d800); + + return std::make_pair(high, low); + } + }(); + + if constexpr (not Pure) + { + if (const auto mask = _kortestz_mask32_u8(high_surrogate_mask, low_surrogate_mask); + mask == 0) + { + // handle surrogates + + const auto this_surrogate_carry = static_cast((high_surrogate_mask >> 30) & 0x01); + + if constexpr (not Correct) + { + // check for mismatched surrogates + if (((high_surrogate_mask << 1) | +(surrogate_carry)) ^ low_surrogate_mask) + { + const auto low_no_high = low_surrogate_mask & ~((high_surrogate_mask << 1) | +(surrogate_carry)); + const auto high_no_low = high_surrogate_mask & ~(low_surrogate_mask >> 1); + const auto length = std::countr_zero(low_no_high | high_no_low); + + return process_result + { + .processed_input = static_cast(length), + .num_output = 0, + .surrogate_carry = surrogate_carry, + .error = true + }; + } + } + + const auto high_surrogate_mask_high = static_cast<__mmask16>(high_surrogate_mask >> 16); + const auto high_surrogate_mask_low = static_cast<__mmask16>(high_surrogate_mask); + + // ReSharper disable CommentTypo + + // Input surrogate pair: + // |1101.11aa.aaaa.aaaa|1101.10bb.bbbb.bbbb| + // low surrogate high surrogate + + // Expand all code units to 32-bit code units + // in > |0000.0000.0000.0000.1101.11aa.aaaa.aaaa|0000.0000.0000.0000.1101.10bb.bbbb.bbbb| + const auto low = _mm512_cvtepu16_epi32(_mm512_castsi512_si256(data)); + const auto high = _mm512_cvtepu16_epi32(_mm512_extracti32x8_epi32(data, 1)); + + // Shift by one 16-bit word to align low surrogates with high surrogates + // in > |0000.0000.0000.0000.1101.11aa.aaaa.aaaa|0000.0000.0000.0000.1101.10bb.bbbb.bbbb| + // shifted > |????. ????. ????. ????. ????. ????.????. ???? |0000.0000.0000.0000.1101.11aa. aaaa. aaaa| + const auto shifted_low = _mm512_alignr_epi32(high, low, 1); + const auto shifted_high = _mm512_alignr_epi32(_mm512_setzero_si512(), high, 1); + + // Align all high surrogates in low and high by shifting to the left by 10 bits + // |0000.0000.0000.0000.1101.11aa.aaaa.aaaa|0000.0011.0110.bbbb.bbbb.bb00.0000.0000| + const auto aligned_low = _mm512_mask_slli_epi32(low, high_surrogate_mask_low, low, 10); + const auto aligned_high = _mm512_mask_slli_epi32(high, high_surrogate_mask_high, high, 10); + + // Remove surrogate prefixes and add offset 0x10000 by adding in, shifted and constant in + // constant > |1111.1100.1010.0000.0010.0100.0000.0000|1111.1100.1010.0000.0010. 0100.0000. 0000| + // in > |0000.0000.0000.0000.1101.11aa.aaaa.aaaa |0000.0011.0110.bbbb.bbbb.bb00.0000.0000| + // shifted > |????. ????. ????. ????. ????. ????.????. ???? |0000.0000.0000.0000.1101. 11aa. aaaa. aaaa| + const auto constant = _mm512_set1_epi32(static_cast(0b1111'1100'1010'0000'0010'0100'0000'0000)); + + // ReSharper restore CommentTypo + + const auto added_low = _mm512_mask_add_epi32(aligned_low, high_surrogate_mask_low, aligned_low, shifted_low); + const auto added_high = _mm512_mask_add_epi32(aligned_high, high_surrogate_mask_high, aligned_high, shifted_high); + + const auto utf32_low = _mm512_mask_add_epi32(added_low, high_surrogate_mask_low, added_low, constant); + const auto utf32_high = _mm512_mask_add_epi32(added_high, high_surrogate_mask_high, added_high, constant); + + const auto valid = ~low_surrogate_mask & data_mask; + const auto valid_high = static_cast<__mmask16>(valid >> 16); + const auto valid_low = static_cast<__mmask16>(valid); + + const auto output_low = _mm512_maskz_compress_epi32(valid_low, utf32_low); + const auto output_high = _mm512_maskz_compress_epi32(valid_high, utf32_high); + + const auto low_length = std::popcount(valid_low); + const auto high_length = std::popcount(valid_high); + const auto low_mask = static_cast<__mmask16>(_pext_u32(valid_low, valid_low)); + const auto high_mask = static_cast<__mmask16>(_pext_u32(valid_high, valid_high)); + + // [in][in]...[in][in][0][0]...[0][0](1) + [in][in]...[in][in][0][0]...[0][0](2) ==> + // [out][out]...[out][out][0][0]...[0][0](1) ==> + // [out][out]...[out][out](1)[out][out]...[out][out](2) + if constexpr (MaskOut) + { + // is the second half of the input vector used? + if (data_length > 16) + { + _mm512_mask_storeu_epi32(it_output_current + 0, low_mask, output_low); + _mm512_mask_storeu_epi32(it_output_current + low_length, high_mask, output_high); + } + else + { + _mm512_mask_storeu_epi32(it_output_current + 0, low_mask, output_low); + } + + return process_result + { + .processed_input = static_cast(data_length), + .num_output = static_cast(low_length + high_length), + .surrogate_carry = this_surrogate_carry, + .error = false + }; + } + else + { + _mm512_storeu_si512(it_output_current + 0, output_low); + _mm512_mask_storeu_epi32(it_output_current + low_length, high_mask, output_high); + + return process_result + { + // keep an overlap of one code unit + .processed_input = static_cast(advance_keep_high_surrogate), + // overwrite last one code unit + .num_output = static_cast(low_length + high_length - 1), + .surrogate_carry = this_surrogate_carry, + .error = false + }; + } + } + } + + // no surrogates + + // 0~15 + const auto out_low = _mm512_cvtepu16_epi32(_mm512_castsi512_si256(data)); + // 16~31 + const auto out_high = _mm512_cvtepu16_epi32(_mm512_extracti32x8_epi32(data, 1)); + + if constexpr (MaskOut) + { + // [in][in][in]...[in][0][0]...[0][0] ==> + // [out][out][out]...[out] + + const auto valid = ~low_surrogate_mask & data_mask; + const auto valid_high = static_cast<__mmask16>(valid >> 16); + const auto valid_low = static_cast<__mmask16>(valid); + + const auto low_length = std::popcount(valid_low); + const auto high_length = std::popcount(valid_high); + const auto low_mask = static_cast<__mmask16>(_pext_u32(valid_low, valid_low)); + const auto high_mask = static_cast<__mmask16>(_pext_u32(valid_high, valid_high)); + + _mm512_mask_storeu_epi32(it_output_current + 0, low_mask, out_low); + _mm512_mask_storeu_epi32(it_output_current + low_length, high_mask, out_high); + + return process_result + { + .processed_input = static_cast(data_length), + .num_output = static_cast(low_length + high_length), + .surrogate_carry = 0, + .error = false + }; + } + else + { + // [in][in][in]...[in][in][in] ==> + // [out][out][out]...[out][out][out] + + _mm512_storeu_si512(it_output_current + 0, out_low); + _mm512_storeu_si512(it_output_current + advance_of_utf32 / 2, out_high); + + return process_result + { + .processed_input = static_cast(advance_of_utf32), + .num_output = static_cast(advance_of_utf32), + .surrogate_carry = 0, + .error = false + }; + } + }; + + std::uint8_t surrogate_carry = 0; + while (it_input_current + advance_of_utf32 <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_utf32}; + #endif + + const auto data = utf16::read_native(it_input_current); + + if (const auto result = do_process.template operator()(data, data_length_full_block, surrogate_carry); + result.error) + { + // surrogate mismatch + + const auto valid_mask = _bzhi_u32(~static_cast(0), static_cast(result.processed_input)); + const auto valid_data = _mm512_maskz_mov_epi16(valid_mask, data); + const auto valid_result = do_process.template operator()(valid_data, result.processed_input, surrogate_carry); + + it_input_current += valid_result.processed_input; + it_output_current += valid_result.num_output; + + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + return {.error = ErrorCode::SURROGATE, .input = current_input_length, .output = current_output_length}; + } + else + { + it_input_current += result.processed_input; + it_output_current += result.num_output; + surrogate_carry = result.surrogate_carry; + } + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_utf32); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto data = utf16::read_native(it_input_current, static_cast(remaining)); + + if (const auto result = do_process.template operator()(data, static_cast(remaining), surrogate_carry); + result.error) + { + // surrogate mismatch + + const auto valid_mask = _bzhi_u32(~static_cast(0), static_cast(result.processed_input)); + const auto valid_data = _mm512_maskz_mov_epi16(valid_mask, data); + const auto valid_result = do_process.template operator()(valid_data, result.processed_input, surrogate_carry); + + it_input_current += valid_result.processed_input; + it_output_current += valid_result.num_output; + + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + return {.error = ErrorCode::SURROGATE, .input = current_input_length, .output = current_output_length}; + } + else + { + it_input_current += result.processed_input; + it_output_current += result.num_output; + surrogate_carry = result.surrogate_carry; + } + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + constexpr auto flip( + const output_type_of::pointer output, + const input_type_of input + ) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const auto it_output_begin = output; + auto it_output_current = it_output_begin; + + while (it_input_current + advance_of_utf16 <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_utf16}; + #endif + + const auto data = utf16::read_native(it_input_current); + + _mm512_storeu_si512(it_output_current, data); + + it_input_current += advance_of_utf16; + it_output_current += advance_of_utf16; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_utf16); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto mask = _bzhi_u32(~static_cast(0), static_cast(remaining)); + const auto data = utf16::read_native(it_input_current, static_cast(remaining)); + + _mm512_mask_storeu_epi16(it_output_current, mask, data); + } + } + + template + requires ( + InputType == CharsType::UTF16_LE and + OutputType == CharsType::UTF16_BE + ) or + ( + InputType == CharsType::UTF16_BE and + OutputType == CharsType::UTF16_LE + ) + constexpr auto transform( + const typename output_type_of::pointer output, + const input_type_of input + ) noexcept -> result_error_input_type + { + if (const auto result = icelake::validate(input); + result.has_error()) + { + icelake::flip(output, {input.data(), result.input}); + return {.error = result.error, .input = result.input}; + } + + icelake::flip(output, input); + return {.error = ErrorCode::NONE, .input = input.size()}; + } + } + } + + namespace utf32 + { + using input_type = chars::utf32::input_type; + // using char_type = chars::utf32::char_type; + using size_type = chars::utf32::size_type; + using pointer_type = chars::utf32::pointer_type; + + constexpr auto advance_of_latin = sizeof(data_type) / sizeof(input_type_of::value_type); + constexpr auto advance_of_utf8 = sizeof(data_type) / sizeof(input_type_of::value_type); + constexpr auto advance_of_utf16 = sizeof(data_type) / sizeof(input_type_of::value_type); + constexpr auto advance_of_utf32 = sizeof(data_type) / sizeof(input_type_of::value_type); + + namespace icelake + { + [[nodiscard]] constexpr auto validate(const input_type input) noexcept -> result_error_input_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const auto do_check = [&](const data_type data) noexcept -> result_error_input_type + { + const auto offset = _mm512_set1_epi32(static_cast(0xffff'2000)); + const auto standard_max = _mm512_set1_epi32(0x0010'ffff); + const auto standard_offset_max = _mm512_set1_epi32(static_cast(0xffff'f7ff)); + + const auto value_offset = _mm512_add_epi32(data, offset); + + const auto outside_range = _mm512_cmpgt_epu32_mask(data, standard_max); + const auto surrogate_range = _mm512_cmpgt_epu32_mask(value_offset, standard_offset_max); + + if (outside_range | surrogate_range) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + + const auto outside_index = std::countr_zero(outside_range); + const auto surrogate_index = std::countr_zero(surrogate_range); + + if (outside_index < surrogate_index) + { + return {.error = ErrorCode::TOO_LARGE, .input = current_input_length + outside_index}; + } + return {.error = ErrorCode::SURROGATE, .input = current_input_length + surrogate_index}; + } + + return {.error = ErrorCode::NONE, .input = input_length}; + }; + + while (it_input_current + advance_of_utf32 <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_utf32}; + #endif + + const auto data = _mm512_loadu_si512(it_input_current); + + if (const auto result = do_check(data); + result.has_error()) + { + return result; + } + + it_input_current += advance_of_utf32; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_utf32); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto mask = static_cast<__mmask16>(_bzhi_u32(~static_cast(0), static_cast(remaining))); + const auto data = _mm512_maskz_loadu_epi32(mask, it_input_current); + + if (const auto result = do_check(data); + result.has_error()) + { + return result; + } + + it_input_current += remaining; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + return {.error = ErrorCode::NONE, .input = current_input_length}; + } + + template + [[nodiscard]] constexpr auto length(const input_type input) noexcept -> size_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + // ReSharper disable CppClangTidyBugproneBranchClone + if constexpr (OutputType == CharsType::LATIN) + { + return input.size(); + } + // ReSharper restore CppClangTidyBugproneBranchClone + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + // ReSharper disable CppInconsistentNaming + // ReSharper disable IdentifierTypo + + // Max code point for 1-byte UTF-8 + const auto v_007f = _mm512_set1_epi32(0x007f); + // Max code point for 2-byte UTF-8 + const auto v_07ff = _mm512_set1_epi32(0x07ff); + // Max code point for 3-byte UTF-8 + const auto v_ffff = _mm512_set1_epi32(0xffff); + + // ReSharper restore IdentifierTypo + // ReSharper restore CppInconsistentNaming + + auto output_length = static_cast(0); + while (it_input_current + advance_of_utf8 <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_utf8}; + #endif + + const auto data = _mm512_loadu_si512(it_input_current); + + const auto ascii_bitmask = _mm512_cmple_epu32_mask( + data, + v_007f + ); + const auto two_bytes_bitmask = _mm512_mask_cmple_epu32_mask( + _mm512_knot(ascii_bitmask), + data, + v_07ff + ); + const auto three_bytes_bitmask = _mm512_mask_cmple_epu32_mask( + _mm512_knot(_mm512_kor(ascii_bitmask, two_bytes_bitmask)), + data, + v_ffff + ); + + const auto ascii_count = std::popcount(ascii_bitmask); + const auto two_bytes_count = std::popcount(two_bytes_bitmask); + const auto three_bytes_count = std::popcount(three_bytes_bitmask); + const auto four_bytes_count = advance_of_utf8 - ascii_count - two_bytes_count - three_bytes_count; + + output_length += ascii_count + 2 * two_bytes_count + 3 * three_bytes_count + 4 * four_bytes_count; + it_input_current += advance_of_utf8; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_utf8); + + if (remaining != 0) + { + // fallback + output_length += Scalar::length({it_input_current, static_cast(remaining)}); + } + + return output_length; + } + else if constexpr (OutputType == CharsType::UTF16_LE or OutputType == CharsType::UTF16_BE or OutputType == CharsType::UTF16) + { + // ReSharper disable CppInconsistentNaming + // ReSharper disable IdentifierTypo + + // For each 32-bit code point c: + // - If c <= 0xffff, it fits in a single UTF-16 unit. + // - If c > 0xffff (i.e. in [0x10000..0x10ffff]), it requires two UTF-16 units (a surrogate pair). + const auto v_ffff = _mm512_set1_epi32(0xffff); + + // ReSharper restore IdentifierTypo + // ReSharper restore CppInconsistentNaming + + auto output_length = static_cast(0); + while (it_input_current + advance_of_utf16 <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, advance_of_utf16}; + #endif + + const auto data = _mm512_loadu_si512(it_input_current); + + const auto surrogates_bitmask = _mm512_cmpgt_epu32_mask(data, v_ffff); + // 1 + (surrogate ? 1 : 0) + output_length += advance_of_utf16 + std::popcount(surrogates_bitmask); + + it_input_current += advance_of_utf16; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_utf16); + + if (remaining != 0) + { + // fallback + output_length += Scalar::length({it_input_current, static_cast(remaining)}); + } + + return output_length; + } + // ReSharper disable CppClangTidyBugproneBranchClone + else if constexpr (OutputType == CharsType::UTF32) + { + return input.size(); + } + // ReSharper restore CppClangTidyBugproneBranchClone + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template + requires ( + OutputType == CharsType::LATIN + ) + [[nodiscard]] constexpr auto write_latin( + const typename output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + // ReSharper disable CppInconsistentNaming + // ReSharper disable IdentifierTypo + + // Max code point for Latin1 + const auto v_00ff = _mm512_set1_epi32(0x00ff); + + // Gathers the lowest byte of each 32-bit code point in 'data' into a contiguous 16-byte __m128i register. + const auto shuffle_mask = _mm512_set_epi8( + // clang-format off + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 60, 56, 52, 48, 44, 40, 36, 32, 28, 24, 20, 16, 12, 8u, 04, 00 + // clang-format on + ); + + // ReSharper restore IdentifierTypo + // ReSharper restore CppInconsistentNaming + + const auto write_tail_block = [&](const __mmask16 mask) noexcept -> result_error_input_output_type + { + if constexpr (Correct) + { + std::ignore = mask; + GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); + } + else + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(mask != 0); + + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + const auto extra = static_cast(std::countr_zero(mask)); + const auto result = Scalar::convert(it_output_current, {it_input_current, extra}); + + if constexpr (Pure) + { + return {.error = ErrorCode::TOO_LARGE, .input = current_input_length + result.input, .output = current_output_length + result.input}; + } + else + { + return {.error = ErrorCode::TOO_LARGE, .input = current_input_length + result.input, .output = current_output_length + result.output}; + } + } + }; + + while (it_input_current + advance_of_latin <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_latin}; + #endif + + const auto data = _mm512_loadu_si512(it_input_current); + + if (const auto mask = _mm512_cmpgt_epu32_mask(data, v_00ff); + mask != 0) + { + return write_tail_block(mask); + } + + const auto out = _mm512_castsi512_si128(_mm512_permutexvar_epi8(shuffle_mask, data)); + auto* p = GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m128i*, it_output_current); + _mm_storeu_si128(p, out); + + it_input_current += advance_of_latin; + it_output_current += advance_of_latin; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_latin); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto mask = static_cast<__mmask16>(_bzhi_u32(~static_cast(0), static_cast(remaining))); + const auto data = _mm512_maskz_loadu_epi32(mask, it_input_current); + + if (const auto latin_mask = _mm512_cmpgt_epu32_mask(data, v_00ff); + latin_mask != 0) + { + return write_tail_block(latin_mask); + } + + const auto out = _mm512_castsi512_si128(_mm512_permutexvar_epi8(shuffle_mask, data)); + _mm_mask_storeu_epi8(it_output_current, mask, out); + + it_input_current += remaining; + it_output_current += remaining; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + template + requires ( + OutputType == CharsType::UTF8_CHAR or + OutputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto write_utf8( + const typename output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + // ReSharper disable CppInconsistentNaming + // ReSharper disable IdentifierTypo + // ReSharper disable CommentTypo + + // const auto v_0010_ffff = _mm256_set1_epi32(0x0010'ffff); + const auto v_7fff_ffff = _mm256_set1_epi32(0x7fff'ffff); + const auto v_0000_ff80 = _mm256_set1_epi16(static_cast(0x0000'ff80)); + const auto v_0000_0000 = _mm256_setzero_si256(); + const auto v_0000_f800 = _mm256_set1_epi16(static_cast(0x0000'f800)); + const auto v_0000_1f00 = _mm256_set1_epi16(0x0000'1f00); + const auto v_0000_003f = _mm256_set1_epi16(0x0000'003f); + const auto v_0000_c080 = _mm256_set1_epi16(static_cast(0x0000'c080)); + const auto v_ffff_0000 = _mm256_set1_epi32(static_cast(0xffff'0000)); + const auto v_0000_d800 = _mm256_set1_epi16(static_cast(0x0000'd800)); + + // ReSharper restore CommentTypo + // ReSharper restore IdentifierTypo + // ReSharper restore CppInconsistentNaming + + const auto write_tail_block = [&](const __mmask16 mask, const ErrorCode error) noexcept -> result_error_input_output_type + { + if constexpr (Correct) + { + std::ignore = mask; + std::ignore = error; + GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); + } + else + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(mask != 0); + + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + const auto extra = static_cast(std::countr_zero(mask)); + const auto result = Scalar::convert(it_output_current, {it_input_current, extra}); + + if constexpr (Pure) + { + return {.error = error, .input = current_input_length + result.input, .output = current_output_length + result.input}; + } + else + { + return {.error = error, .input = current_input_length + result.input, .output = current_output_length + result.output}; + } + } + }; + + while (it_input_current + advance_of_utf8 <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_utf8}; + #endif + + const auto data = _mm512_loadu_si512(it_input_current); + const auto low = _mm512_castsi512_si256(data); + const auto high = _mm512_extracti64x4_epi64(data, 1); + + if constexpr (not Correct) + { + // ReSharper disable CommentTypo + + // Check for too large input + // if (const auto mask = _mm512_cmpgt_epu32_mask(data, v_0010_ffff); + if (const auto mask = _mm512_cmpgt_epu32_mask(data, _mm512_set1_epi32(0x0010'ffff)); + mask != 0) + { + return write_tail_block(mask, ErrorCode::TOO_LARGE); + } + + // ReSharper restore CommentTypo + } + + // Pack 32-bit UTF-32 code units to 16-bit UTF-16 code units with unsigned saturation + const auto in_16_packed = _mm256_packus_epi32( + _mm256_and_si256(low, v_7fff_ffff), + _mm256_and_si256(high, v_7fff_ffff) + ); + const auto in_16 = _mm256_permute4x64_epi64(in_16_packed, 0b1101'1000); + + // Try to apply UTF-16 => UTF-8 routine on 256 bits + if (_mm256_testz_si256(in_16, v_0000_ff80)) + { + // ascii only + + const auto in_16_low = _mm256_castsi256_si128(in_16); + const auto in_16_high = _mm256_extracti128_si256(in_16, 1); + + // pack the bytes + const auto utf8_packed = _mm_packus_epi16(in_16_low, in_16_high); + + // store 16 bytes + auto* p = GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m128i*, it_output_current); + _mm_storeu_si128(p, utf8_packed); + + it_input_current += advance_of_utf8; + it_output_current += advance_of_utf8; + + // we are done for this round + continue; + } + + // no bits set above 7th bit + const auto one_byte_byte_mask = _mm256_cmpeq_epi16(_mm256_and_si256(in_16, v_0000_ff80), v_0000_0000); + const auto one_byte_bit_mask = static_cast(_mm256_movemask_epi8(one_byte_byte_mask)); + + // no bits set above 11th bit + const auto one_or_two_bytes_byte_mask = _mm256_cmpeq_epi16(_mm256_and_si256(in_16, v_0000_f800), v_0000_0000); + const auto one_or_two_bytes_bit_mask = static_cast(_mm256_movemask_epi8(one_or_two_bytes_byte_mask)); + + if (one_or_two_bytes_bit_mask == 0xffff'ffff) + { + // ReSharper disable CommentTypo + + // prepare 2-bytes values + // input 16-bit word : [0000|0aaa|aabb|bbbb] x 8 + // expected output : [110a|aaaa|10bb|bbbb] x 8 + + // t0 = [000a|aaaa|bbbb|bb00] + const auto t0 = _mm256_slli_epi16(in_16, 2); + // t1 = [000a|aaaa|0000|0000] + const auto t1 = _mm256_and_si256(t0, v_0000_1f00); + // t2 = [0000|0000|00bb|bbbb] + const auto t2 = _mm256_and_si256(in_16, v_0000_003f); + // t3 = [000a|aaaa|00bb|bbbb] + const auto t3 = _mm256_or_si256(t1, t2); + // t4 = [110a|aaaa|10bb|bbbb] + const auto t4 = _mm256_or_si256(t3, v_0000_c080); + + // ReSharper restore CommentTypo + + // merge ascii and 2-bytes codewords + const auto utf8_unpacked = _mm256_blendv_epi8(t4, in_16, one_byte_byte_mask); + + // prepare bitmask for 8-bits lookup + const auto mask_0 = static_cast(one_byte_bit_mask & 0x5555'5555); + const auto mask_1 = static_cast(mask_0 >> 7); + const auto mask = static_cast((mask_0 | mask_1) & 0x00ff'00ff); + + // pack the bytes + using type = std::decay_t; + // length + data + static_assert(std::ranges::size(type{}) == 1 + advance_of_utf8); + + const auto index_0 = static_cast(mask); + const auto index_1 = static_cast(mask >> 16); + + const auto& data_0 = detail::utf32::icelake::utf16_to_utf8::_1_2[index_0]; + const auto& data_1 = detail::utf32::icelake::utf16_to_utf8::_1_2[index_1]; + + const auto length_0 = data_0.front(); + const auto length_1 = data_1.front(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length_0 <= advance_of_utf8); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length_1 <= advance_of_utf8); + + const auto* row_0 = data_0.data() + 1; + const auto* row_1 = data_1.data() + 1; + + const auto shuffle_0 = _mm_loadu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m128i*, row_0)); + const auto shuffle_1 = _mm_loadu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m128i*, row_1)); + + const auto utf8_packed = _mm256_shuffle_epi8(utf8_unpacked, _mm256_setr_m128i(shuffle_0, shuffle_1)); + + const auto utf8_packed_low = _mm256_castsi256_si128(utf8_packed); + const auto utf8_packed_high = _mm256_extracti128_si256(utf8_packed, 1); + + // store the bytes + auto* p = GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m128i*, it_output_current); + + _mm_storeu_si128(p, utf8_packed_low); + it_output_current += length_0; + _mm_storeu_si128(p, utf8_packed_high); + it_output_current += length_1; + + it_input_current += advance_of_utf8; + + // we are done for this round + continue; + } + + // check for overflow in packing + const auto saturation_byte_mask = _mm256_cmpeq_epi32(_mm256_and_si256(_mm256_or_si256(low, high), v_ffff_0000), v_0000_0000); + const auto saturation_bit_mask = static_cast(_mm256_movemask_epi8(saturation_byte_mask)); + + if (saturation_bit_mask == 0xffff'ffff) + { + // code units from register produce either 1, 2 or 3 UTF-8 bytes + + if constexpr (not Correct) + { + const auto forbidden_byte_mask = _mm256_cmpeq_epi16(_mm256_and_si256(in_16, v_0000_f800), v_0000_d800); + if (const auto mask = _mm256_movepi16_mask(forbidden_byte_mask); + mask != 0) + { + return write_tail_block(mask, ErrorCode::SURROGATE); + } + } + + // ReSharper disable CommentTypo + + // In this branch we handle three cases: + // 1. [0000|0000|0ccc|cccc] => [0ccc|cccc] - single UFT-8 byte + // 2. [0000|0bbb|bbcc|cccc] => [110b|bbbb], [10cc|cccc] - two UTF-8 bytes + // 3. [aaaa|bbbb|bbcc|cccc] => [1110|aaaa], [10bb|bbbb], [10cc|cccc] - three UTF-8 bytes + + // We expand the input word (16-bit) into two code units (32-bit), thus we have room for four bytes. + // However, we need five distinct bit layouts. + // Note that the last byte in cases #2 and #3 is the same. + + // We precompute byte 1 for case #1 and the common byte for cases #2 and #3 in register t2. + // We precompute byte 1 for case #3 and -- **conditionally** -- precompute either byte 1 for case #2 or byte 2 for case #3. + // Note that they differ by exactly one bit. + // Finally, from these two code units we build proper UTF-8 sequence, taking into account the case (i.e, the number of bytes to write). + + // input : [aaaa|bbbb|bbcc|cccc] + // output : + // t2 => [0ccc|cccc] [10cc|cccc] + // s4 => [1110|aaaa] ([110b|bbbb] or [10bb|bbbb]) + + const auto dup_even = _mm256_setr_epi16( + // clang-format off + 0x0000, 0x0202, 0x0404, 0x0606, + 0x0808, 0x0a0a, 0x0c0c, 0x0e0e, + 0x0000, 0x0202, 0x0404, 0x0606, + 0x0808, 0x0a0a, 0x0c0c, 0x0e0e + // clang-format on + ); + + // [aaaa|bbbb|bbcc|cccc] => [bbcc|cccc|bbcc|cccc] + const auto t0 = _mm256_shuffle_epi8(in_16, dup_even); + // [bbcc|cccc|bbcc|cccc] => [00cc|cccc|0bcc|cccc] + const auto t1 = _mm256_and_si256(t0, _mm256_set1_epi16(0b0011'1111'0111'1111)); + // [00cc|cccc|0bcc|cccc] => [10cc|cccc|0bcc|cccc] + const auto t2 = _mm256_or_si256(t1, _mm256_set1_epi16(static_cast(0b1000'0000'0000'0000))); + + // [aaaa|bbbb|bbcc|cccc] => [0000|aaaa|bbbb|bbcc] + const auto s0 = _mm256_srli_epi16(in_16, 4); + // [0000|aaaa|bbbb|bbcc] => [0000|aaaa|bbbb|bb00] + const auto s1 = _mm256_and_si256(s0, _mm256_set1_epi16(0b0000'1111'1111'1100)); + // [0000|aaaa|bbbb|bb00] => [00bb|bbbb|0000|aaaa] + const auto s2 = _mm256_maddubs_epi16(s1, _mm256_set1_epi16(0x0140)); + // [00bb|bbbb|0000|aaaa] => [11bb|bbbb|1110|aaaa] + const auto s3 = _mm256_or_si256(s2, _mm256_set1_epi16(static_cast(0b1100'0000'1110'0000))); + const auto s4 = _mm256_xor_si256( + s3, + _mm256_andnot_si256( + one_or_two_bytes_byte_mask, + _mm256_set1_epi16(0b0100'0000'0000'0000) + ) + ); + + // ReSharper restore CommentTypo + + // expand code units 16-bit => 32-bit + const auto out_0 = _mm256_unpacklo_epi16(t2, s4); + const auto out_1 = _mm256_unpackhi_epi16(t2, s4); + // compress 32-bit code units into 1, 2 or 3 bytes -- 2 x shuffle + const auto mask = + (one_byte_bit_mask & 0x5555'5555) | + (one_or_two_bytes_bit_mask & 0xaaaa'aaaa); + + using type = std::decay_t; + // length + data + static_assert(std::ranges::size(type{}) == 1 + advance_of_utf8); + + const auto index_0 = static_cast(mask); + const auto index_1 = static_cast(mask >> 8); + const auto index_2 = static_cast(mask >> 16); + const auto index_3 = static_cast(mask >> 24); + + const auto& data_0 = detail::utf32::icelake::utf16_to_utf8::_1_2_3[index_0]; + const auto& data_1 = detail::utf32::icelake::utf16_to_utf8::_1_2_3[index_1]; + const auto& data_2 = detail::utf32::icelake::utf16_to_utf8::_1_2_3[index_2]; + const auto& data_3 = detail::utf32::icelake::utf16_to_utf8::_1_2_3[index_3]; + + const auto length_0 = data_0.front(); + const auto length_1 = data_1.front(); + const auto length_2 = data_2.front(); + const auto length_3 = data_3.front(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length_0 <= advance_of_utf8); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length_1 <= advance_of_utf8); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length_2 <= advance_of_utf8); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length_3 <= advance_of_utf8); + + const auto* row_0 = data_0.data() + 1; + const auto* row_1 = data_1.data() + 1; + const auto* row_2 = data_2.data() + 1; + const auto* row_3 = data_3.data() + 1; + + const auto shuffle_0 = _mm_loadu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m128i*, row_0)); + const auto shuffle_1 = _mm_loadu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m128i*, row_1)); + const auto shuffle_2 = _mm_loadu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m128i*, row_2)); + const auto shuffle_3 = _mm_loadu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m128i*, row_3)); + + const auto utf8_0 = _mm_shuffle_epi8(_mm256_castsi256_si128(out_0), shuffle_0); + const auto utf8_1 = _mm_shuffle_epi8(_mm256_castsi256_si128(out_1), shuffle_1); + const auto utf8_2 = _mm_shuffle_epi8(_mm256_extracti128_si256(out_0, 1), shuffle_2); + const auto utf8_3 = _mm_shuffle_epi8(_mm256_extracti128_si256(out_1, 1), shuffle_3); + + // store the bytes + auto* p = GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m128i*, it_output_current); + + _mm_storeu_si128(p, utf8_0); + it_output_current += length_0; + _mm_storeu_si128(p, utf8_1); + it_output_current += length_1; + _mm_storeu_si128(p, utf8_2); + it_output_current += length_2; + _mm_storeu_si128(p, utf8_3); + it_output_current += length_3; + + it_input_current += advance_of_utf8; + + // we are done for this round + continue; + } + + // at least one 32-bit word is larger than 0xffff => it will produce four UTF-8 bytes (like emoji) + // scalar fallback + const auto fallback_end = it_input_current + advance_of_utf8; + while (it_input_current < fallback_end) + { + const auto [length, error] = Scalar::convert(it_output_current, it_input_current, fallback_end); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length == 1); + + if constexpr (Correct) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(error == ErrorCode::NONE); + } + else + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + return {.error = error, .input = current_input_length, .output = current_output_length}; + } + } + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_utf8); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto result = Scalar::convert(it_output_current, {it_input_current, static_cast(remaining)}); + + if constexpr (Correct) + { + it_input_current += remaining; + it_output_current += result.output; + } + else if constexpr (Pure) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not result.has_error()); + it_input_current += result.input; + it_output_current += result.input; + } + else + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not result.has_error()); + it_input_current += result.input; + it_output_current += result.output; + } + + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + if constexpr (Correct) + { + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + else + { + return {.error = result.error, .input = current_input_length, .output = current_output_length}; + } + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + template + requires ( + OutputType == CharsType::UTF16_LE or + OutputType == CharsType::UTF16_BE + ) + [[nodiscard]] constexpr auto write_utf16( + const typename output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + // ReSharper disable CppInconsistentNaming + // ReSharper disable IdentifierTypo + + const auto v_ffff_0000 = _mm512_set1_epi32(static_cast(0xffff'0000)); + const auto v_0000_0000 = _mm512_setzero_si512(); + const auto v_0000_f800 = _mm512_set1_epi16(static_cast(0x0000'f800)); + const auto v_0000_d800 = _mm512_set1_epi16(static_cast(0x0000'd800)); + const auto v_0010_ffff = _mm512_set1_epi32(0x0010'ffff); + const auto v_0001_0000 = _mm512_set1_epi32(0x0001'0000); + const auto v_03ff_0000 = _mm512_set1_epi32(0x03ff'0000); + const auto v_0000_03ff = _mm512_set1_epi32(0x0000'03ff); + const auto v_dc00_d800 = _mm512_set1_epi32(static_cast(0xdc00'd800)); + + // ReSharper restore IdentifierTypo + // ReSharper restore CppInconsistentNaming + + const auto to_native_utf16 = functional::overloaded{ + [](const data_type data) noexcept -> data_type + { + if constexpr (common::not_native_endian()) + { + const auto shuffle_mask = _mm512_set_epi8( + // clang-format off + 14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1, + 14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1, + 14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1, + 14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1 + // clang-format on + ); + + return _mm512_shuffle_epi8(data, shuffle_mask); + } + else + { + return data; + } + }, + [](const __m256i data) noexcept -> __m256i + { + if constexpr (common::not_native_endian()) + { + const auto shuffle_mask = _mm256_setr_epi8( + // clang-format off + 01, 00, 03, 02, 05, 04, 07, 06, 9u, 8u, 11, 10, 13, 12, 15, 14, + 01, 00, 03, 02, 05, 04, 07, 06, 9u, 8u, 11, 10, 13, 12, 15, 14 + // clang-format on + ); + + return _mm256_shuffle_epi8(data, shuffle_mask); + } + else + { + return data; + } + } + }; + + const auto do_write_surrogate = [&](const data_type data, const __mmask16 surrogate_mask, const __mmask32 out_mask) noexcept -> size_type + { + const auto sub = _mm512_mask_sub_epi32(data, surrogate_mask, data, v_0001_0000); + + auto v1 = _mm512_mask_slli_epi32(sub, surrogate_mask, sub, 16); + v1 = _mm512_mask_and_epi32(sub, surrogate_mask, v1, v_03ff_0000); + auto v2 = _mm512_mask_srli_epi32(sub, surrogate_mask, sub, 10); + v2 = _mm512_mask_and_epi32(sub, surrogate_mask, v2, v_0000_03ff); + + const auto v = _mm512_or_si512(v1, v2); + const auto out = _mm512_mask_add_epi32(sub, surrogate_mask, v, v_dc00_d800); + const auto native_out = to_native_utf16(out); + + const auto num_out = std::popcount(out_mask); + const auto num_out_mask = _bzhi_u32(~static_cast(0), static_cast(num_out)); + + // fixme + // _mm512_mask_compressstoreu_epi16(it_output_current, out_mask, native_out); + _mm512_mask_storeu_epi16( + it_output_current, + num_out_mask, + _mm512_maskz_compress_epi16(out_mask, native_out) + ); + + it_output_current += num_out; + return num_out; + }; + + while (it_input_current + advance_of_utf16 <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance_of_utf16}; + #endif + + const auto data = _mm512_loadu_si512(it_input_current); + + if constexpr (Pure) + { + const auto out = _mm512_cvtepi32_epi16(data); + const auto native_out = to_native_utf16(out); + + auto* p = GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m256i*, it_output_current); + _mm256_storeu_si256(p, native_out); + + it_input_current += advance_of_utf16; + it_output_current += advance_of_utf16; + } + else + { + // no bits set above 16th bit => can pack to UTF16 without surrogate pairs + if (const auto saturation_mask = _mm512_cmpeq_epi32_mask(_mm512_and_si512(data, v_ffff_0000), v_0000_0000); + saturation_mask == 0xffff) + { + const auto forbidden_byte_mask = _mm512_cmpeq_epi32_mask(_mm512_and_si512(data, v_0000_f800), v_0000_d800); + + const auto utf16_packed = [=]() noexcept + { + const auto out = _mm512_cvtepi32_epi16(data); + const auto native_out = to_native_utf16(out); + + return native_out; + }(); + + if (Correct) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(forbidden_byte_mask == 0); + } + else + { + if (forbidden_byte_mask != 0) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + const auto extra = std::countr_zero(forbidden_byte_mask); + const auto extra_mask = static_cast<__mmask16>(_blsmsk_u32(forbidden_byte_mask) >> 1); + + _mm256_mask_storeu_epi16(it_output_current, extra_mask, utf16_packed); + + return {.error = ErrorCode::SURROGATE, .input = current_input_length + extra, .output = current_output_length + extra}; + } + } + + auto* p = GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m256i*, it_output_current); + _mm256_storeu_si256(p, utf16_packed); + + it_input_current += advance_of_utf16; + it_output_current += advance_of_utf16; + } + else + { + auto out_mask = ~_pdep_u32(saturation_mask, 0xaaaa'aaaa); + const auto surrogate_mask = static_cast<__mmask16>(~saturation_mask); + + if constexpr (not Correct) + { + const auto error_surrogate = _mm512_mask_cmpeq_epi32_mask(saturation_mask, _mm512_and_si512(data, v_0000_f800), v_0000_d800); + const auto error_too_large = _mm512_mask_cmpgt_epu32_mask(surrogate_mask, data, v_0010_ffff); + + if (error_surrogate or error_too_large) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + const auto surrogate_index = std::countr_zero(error_surrogate); + const auto outside_index = std::countr_zero(error_too_large); + + if (outside_index < surrogate_index) + { + out_mask &= _bzhi_u32(~static_cast(0), static_cast(2 * outside_index)); + const auto extra = do_write_surrogate(data, surrogate_mask, out_mask); + + return {.error = ErrorCode::TOO_LARGE, .input = current_input_length + outside_index, .output = current_output_length + extra}; + } + + out_mask &= _bzhi_u32(~static_cast(0), static_cast(2 * surrogate_index)); + const auto extra = do_write_surrogate(data, surrogate_mask, out_mask); + return {.error = ErrorCode::SURROGATE, .input = current_input_length + surrogate_index, .output = current_output_length + extra}; + } + } + + std::ignore = do_write_surrogate(data, surrogate_mask, out_mask); + it_input_current += advance_of_utf16; + } + } + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < advance_of_utf16); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto mask = static_cast<__mmask16>(_bzhi_u32(~static_cast(0), static_cast(remaining))); + const auto data = _mm512_maskz_loadu_epi32(mask, it_input_current); + + if constexpr (Pure) + { + const auto out = _mm512_cvtepi32_epi16(data); + const auto native_out = to_native_utf16(out); + + _mm256_mask_storeu_epi16(it_output_current, mask, native_out); + } + else + { + if (const auto saturation_mask = static_cast<__mmask16>(_mm512_cmpeq_epi32_mask(_mm512_and_si512(data, v_ffff_0000), v_0000_0000) & mask); + saturation_mask == mask) + { + const auto forbidden_byte_mask = _mm512_cmpeq_epi32_mask(_mm512_and_si512(data, v_0000_f800), v_0000_d800); + + const auto utf16_packed = [=]() noexcept + { + const auto out = _mm512_cvtepi32_epi16(data); + const auto native_out = to_native_utf16(out); + + return native_out; + }(); + + if (Correct) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(forbidden_byte_mask == 0); + } + else + { + if (forbidden_byte_mask != 0) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + const auto extra = std::countr_zero(forbidden_byte_mask); + const auto extra_mask = static_cast<__mmask16>(_blsmsk_u32(forbidden_byte_mask) >> 1); + + _mm256_mask_storeu_epi16(it_output_current, extra_mask, utf16_packed); + + return {.error = ErrorCode::SURROGATE, .input = current_input_length + extra, .output = current_output_length + extra}; + } + } + + _mm256_mask_storeu_epi16(it_output_current, mask, utf16_packed); + + it_input_current += remaining; + it_output_current += remaining; + } + else + { + const auto out_max_mask = _bzhi_u32(~static_cast(0), static_cast(2 * remaining)); + + auto out_mask = ~_pdep_u32(saturation_mask, 0xaaaa'aaaa) & out_max_mask; + const auto surrogate_mask = static_cast<__mmask16>(static_cast<__mmask16>(~saturation_mask) & mask); + + if constexpr (not Correct) + { + const auto error_surrogate = _mm512_mask_cmpeq_epi32_mask(saturation_mask, _mm512_and_si512(data, v_0000_f800), v_0000_d800); + const auto error_too_large = _mm512_mask_cmpgt_epu32_mask(surrogate_mask, data, v_0010_ffff); + + if (error_surrogate or error_too_large) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + const auto surrogate_index = std::countr_zero(error_surrogate); + const auto outside_index = std::countr_zero(error_too_large); + + if (outside_index < surrogate_index) + { + out_mask &= _bzhi_u32(~static_cast(0), static_cast(2 * outside_index)); + const auto extra = do_write_surrogate(data, surrogate_mask, out_mask); + + return {.error = ErrorCode::TOO_LARGE, .input = current_input_length + outside_index, .output = current_output_length + extra}; + } + + out_mask &= _bzhi_u32(~static_cast(0), static_cast(2 * surrogate_index)); + const auto extra = do_write_surrogate(data, surrogate_mask, out_mask); + return {.error = ErrorCode::SURROGATE, .input = current_input_length + surrogate_index, .output = current_output_length + extra}; + } + } + + std::ignore = do_write_surrogate(data, surrogate_mask, out_mask); + it_input_current += remaining; + } + } + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + } + } +} + +namespace gal::prometheus::chars +{ + namespace latin::icelake + { + auto validate(const input_type input) noexcept -> result_error_input_type + { + return ::latin::icelake::validate(input); + } + + auto validate(const pointer_type input) noexcept -> result_error_input_type + { + return validate({input, std::char_traits::length(input)}); + } + + auto length_for_latin(const input_type input) noexcept -> size_type + { + return input.size(); + } + + auto length_for_latin(pointer_type input) noexcept -> size_type + { + return length_for_latin({input, std::char_traits::length(input)}); + } + + auto length_for_utf8(const input_type input) noexcept -> size_type + { + const auto length = ::latin::icelake::length(input); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length == ::latin::icelake::length(input)); + + return length; + } + + auto length_for_utf8(const pointer_type input) noexcept -> size_type + { + return length_for_utf8({input, std::char_traits::length(input)}); + } + + auto length_for_utf16(const input_type input) noexcept -> size_type + { + const auto length = ::latin::icelake::length(input); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length == ::latin::icelake::length(input)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length == ::latin::icelake::length(input)); + + return length; + } + + auto length_for_utf16(const pointer_type input) noexcept -> size_type + { + return length_for_utf16({input, std::char_traits::length(input)}); + } + + auto length_for_utf32(const input_type input) noexcept -> size_type + { + return ::latin::icelake::length(input); + } + + auto length_for_utf32(const pointer_type input) noexcept -> size_type + { + return length_for_utf32({input, std::char_traits::length(input)}); + } + + auto write_utf8( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::latin::icelake::write_utf8(output, input); + } + + auto write_utf8( + const output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf8(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::latin::icelake::write_utf8(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf8_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::latin::icelake::write_utf8(output, input); + return {.output = result.output}; + } + + auto write_utf8_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf8_correct(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::latin::icelake::write_utf8(output, input); + } + + auto write_utf8( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf8(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::latin::icelake::write_utf8(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf8_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::latin::icelake::write_utf8(output, input); + return {.output = result.output}; + } + + auto write_utf8_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf8_correct(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::latin::icelake::write_utf16(output, input); + } + + auto write_utf16_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf16_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::latin::icelake::write_utf16(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf16_le_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_le_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::latin::icelake::write_utf16(output, input); + return {.output = result.output}; + } + + auto write_utf16_le_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf16_le_correct(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::latin::icelake::write_utf16(output, input); + } + + auto write_utf16_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf16_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::latin::icelake::write_utf16(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf16_be_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_be_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::latin::icelake::write_utf16(output, input); + return {.output = result.output}; + } + + auto write_utf16_be_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf16_be_correct(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::latin::icelake::write_utf32(output, input); + } + + auto write_utf32( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf32(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::latin::icelake::write_utf32(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf32_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf32_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::latin::icelake::write_utf32(output, input); + return {.output = result.output}; + } + + auto write_utf32_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf32_correct(output, {input, std::char_traits::length(input)}); + } + } + + namespace utf8_char::icelake + { + auto validate(const input_type input) noexcept -> result_error_input_type + { + return ::utf8::icelake::validate(input); + } + + auto validate(const pointer_type input) noexcept -> result_error_input_type + { + return validate({input, std::char_traits::length(input)}); + } + + auto length_for_latin(const input_type input) noexcept -> size_type + { + return ::utf8::icelake::length(input); + } + + auto length_for_latin(const pointer_type input) noexcept -> size_type + { + return length_for_latin({input, std::char_traits::length(input)}); + } + + auto length_for_utf8(const input_type input) noexcept -> size_type + { + return input.size(); + } + + auto length_for_utf8(const pointer_type input) noexcept -> size_type + { + return length_for_utf8({input, std::char_traits::length(input)}); + } + + auto length_for_utf16(const input_type input) noexcept -> size_type + { + const auto length = ::utf8::icelake::length(input); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME((length == ::utf8::icelake::length(input))); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME((length == ::utf8::icelake::length(input))); + + return length; + } + + auto length_for_utf16(const pointer_type input) noexcept -> size_type + { + return length_for_utf16({input, std::char_traits::length(input)}); + } + + auto length_for_utf32(const input_type input) noexcept -> size_type + { + return ::utf8::icelake::length(input); + } + + auto length_for_utf32(const pointer_type input) noexcept -> size_type + { + return length_for_utf32({input, std::char_traits::length(input)}); + } + + auto write_latin( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::icelake::write_latin(output, input); + } + + auto write_latin( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_latin(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf8::icelake::write_latin(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_latin_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_latin_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf8::icelake::write_latin(output, input); + return {.output = result.output}; + } + + auto write_latin_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_latin_correct(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::icelake::write_utf16(output, input); + } + + auto write_utf16_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf16_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf8::icelake::write_utf16(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf16_le_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_le_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf8::icelake::write_utf16(output, input); + return {.output = result.output}; + } + + auto write_utf16_le_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf16_le_correct(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::icelake::write_utf16(output, input); + } + + auto write_utf16_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf16_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf8::icelake::write_utf16(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf16_be_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_be_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf8::icelake::write_utf16(output, input); + return {.output = result.output}; + } + + auto write_utf16_be_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf16_be_correct(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::icelake::write_utf32(output, input); + } + + auto write_utf32( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf32(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf8::icelake::write_utf32(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf32_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf32_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf8::icelake::write_utf32(output, input); + return {.output = result.output}; + } + + auto write_utf32_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf32_correct(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + return ::utf8::icelake::transform(output, input); + } + + auto write_utf8( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8(output, {input, std::char_traits::length(input)}); + } + } + + namespace utf8::icelake + { + auto validate(const input_type input) noexcept -> result_error_input_type + { + return ::utf8::icelake::validate(input); + } + + auto validate(const pointer_type input) noexcept -> result_error_input_type + { + return validate({input, std::char_traits::length(input)}); + } + + auto length_for_latin(const input_type input) noexcept -> size_type + { + return ::utf8::icelake::length(input); + } + + auto length_for_latin(const pointer_type input) noexcept -> size_type + { + return length_for_latin({input, std::char_traits::length(input)}); + } + + auto length_for_utf8(const input_type input) noexcept -> size_type + { + return input.size(); + } + + auto length_for_utf8(const pointer_type input) noexcept -> size_type + { + return length_for_utf8({input, std::char_traits::length(input)}); + } + + auto length_for_utf16(const input_type input) noexcept -> size_type + { + const auto length = ::utf8::icelake::length(input); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME((length == ::utf8::icelake::length(input))); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME((length == ::utf8::icelake::length(input))); + + return length; + } + + auto length_for_utf16(const pointer_type input) noexcept -> size_type + { + return length_for_utf16({input, std::char_traits::length(input)}); + } + + auto length_for_utf32(const input_type input) noexcept -> size_type + { + return ::utf8::icelake::length(input); + } + + auto length_for_utf32(pointer_type input) noexcept -> size_type + { + return length_for_utf32({input, std::char_traits::length(input)}); + } + + auto write_latin( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::icelake::write_latin(output, input); + } + + auto write_latin( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_latin(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf8::icelake::write_latin(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_latin_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_latin_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf8::icelake::write_latin(output, input); + return {.output = result.output}; + } + + auto write_latin_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_latin_correct(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::icelake::write_utf16(output, input); + } + + auto write_utf16_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf16_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf8::icelake::write_utf16(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf16_le_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_le_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf8::icelake::write_utf16(output, input); + return {.output = result.output}; + } + + auto write_utf16_le_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf16_le_correct(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::icelake::write_utf16(output, input); + } + + auto write_utf16_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf16_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf8::icelake::write_utf16(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf16_be_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_be_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf8::icelake::write_utf16(output, input); + return {.output = result.output}; + } + + auto write_utf16_be_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf16_be_correct(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::icelake::write_utf32(output, input); + } + + auto write_utf32( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf32(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf8::icelake::write_utf32(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf32_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf32_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf8::icelake::write_utf32(output, input); + return {.output = result.output}; + } + + auto write_utf32_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf32_correct(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + return ::utf8::icelake::transform(output, input); + } + + auto write_utf8( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8(output, {input, std::char_traits::length(input)}); + } + } + + namespace utf16::icelake + { + [[nodiscard]] auto validate_le(const input_type input) noexcept -> result_error_input_type + { + return ::utf16::icelake::validate(input); + } + + [[nodiscard]] auto validate_le(const pointer_type input) noexcept -> result_error_input_type + { + return validate_le({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto validate_be(const input_type input) noexcept -> result_error_input_type + { + return ::utf16::icelake::validate(input); + } + + [[nodiscard]] auto validate_be(const pointer_type input) noexcept -> result_error_input_type + { + return validate_be({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_le_for_latin(const input_type input) noexcept -> size_type + { + return ::utf16::icelake::length(input); + } + + [[nodiscard]] auto length_le_for_latin(const pointer_type input) noexcept -> size_type + { + return length_le_for_latin({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_be_for_latin(const input_type input) noexcept -> size_type + { + return ::utf16::icelake::length(input); + } + + [[nodiscard]] auto length_be_for_latin(const pointer_type input) noexcept -> size_type + { + return length_be_for_latin({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_le_for_utf8(const input_type input) noexcept -> size_type + { + return ::utf16::icelake::length(input); + } + + [[nodiscard]] auto length_le_for_utf8(const pointer_type input) noexcept -> size_type + { + return length_le_for_utf8({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_be_for_utf8(const input_type input) noexcept -> size_type + { + return ::utf16::icelake::length(input); + } + + [[nodiscard]] auto length_be_for_utf8(const pointer_type input) noexcept -> size_type + { + return length_be_for_utf8({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_for_utf16(const input_type input) noexcept -> size_type + { + return input.size(); + } + + [[nodiscard]] auto length_for_utf16(const pointer_type input) noexcept -> size_type + { + return length_for_utf16({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_le_for_utf32(const input_type input) noexcept -> size_type + { + return ::utf16::icelake::length(input); + } + + [[nodiscard]] auto length_le_for_utf32(const pointer_type input) noexcept -> size_type + { + return length_le_for_utf32({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_be_for_utf32(const input_type input) noexcept -> size_type + { + return ::utf16::icelake::length(input); + } + + [[nodiscard]] auto length_be_for_utf32(const pointer_type input) noexcept -> size_type + { + return length_be_for_utf32({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_latin_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf16::icelake::write_latin(output, input); + } + + [[nodiscard]] auto write_latin_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_latin_le(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_latin_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf16::icelake::write_latin(output, input); + } + + [[nodiscard]] auto write_latin_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_latin_be(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_pure_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf16::icelake::write_latin(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_latin_pure_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_latin_pure_le(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_pure_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf16::icelake::write_latin(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_latin_pure_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_latin_pure_be(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_correct_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf16::icelake::write_latin(output, input); + return {.output = result.output}; + } + + auto write_latin_correct_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_latin_correct_le(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_correct_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf16::icelake::write_latin(output, input); + return {.output = result.output}; + } + + auto write_latin_correct_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_latin_correct_be(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf8_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf16::icelake::write_utf8(output, input); + } + + [[nodiscard]] auto write_utf8_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf8_le(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf8_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf16::icelake::write_utf8(output, input); + } + + [[nodiscard]] auto write_utf8_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf8_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_pure_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf16::icelake::write_utf8(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf8_pure_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8_pure_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_pure_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf16::icelake::write_utf8(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf8_pure_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8_pure_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_correct_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf16::icelake::write_utf8(output, input); + return {.output = result.output}; + } + + auto write_utf8_correct_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf8_correct_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_correct_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf16::icelake::write_utf8(output, input); + return {.output = result.output}; + } + + auto write_utf8_correct_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf8_correct_be(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf8_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf16::icelake::write_utf8(output, input); + } + + [[nodiscard]] auto write_utf8_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf8_le(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf8_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf16::icelake::write_utf8(output, input); + } + + [[nodiscard]] auto write_utf8_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf8_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_pure_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf16::icelake::write_utf8(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf8_pure_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8_pure_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_pure_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf16::icelake::write_utf8(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf8_pure_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8_pure_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_correct_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf16::icelake::write_utf8(output, input); + return {.output = result.output}; + } + + auto write_utf8_correct_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf8_correct_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_correct_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf16::icelake::write_utf8(output, input); + return {.output = result.output}; + } + + auto write_utf8_correct_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf8_correct_be(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf32_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf16::icelake::write_utf32(output, input); + } + + [[nodiscard]] auto write_utf32_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf32_le(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf32_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf16::icelake::write_utf32(output, input); + } + + [[nodiscard]] auto write_utf32_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf32_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_pure_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf16::icelake::write_utf32(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf32_pure_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf32_pure_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_pure_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf16::icelake::write_utf32(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf32_pure_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf32_pure_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_correct_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf16::icelake::write_utf32(output, input); + return {.output = result.output}; + } + + auto write_utf32_correct_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf32_correct_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_correct_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf16::icelake::write_utf32(output, input); + return {.output = result.output}; + } + + auto write_utf32_correct_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf32_correct_be(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf16_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + return ::utf16::icelake::transform(output, input); + } + + [[nodiscard]] auto write_utf16_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_le(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf16_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + return ::utf16::icelake::transform(output, input); + } + + [[nodiscard]] auto write_utf16_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_be(output, {input, std::char_traits::length(input)}); + } + + auto flip( + const output_type_of::pointer output, + const input_type input + ) noexcept -> void + { + return ::utf16::icelake::flip(output, input); + } + + auto flip( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> void + { + return flip(output, {input, std::char_traits::length(input)}); + } + } + + namespace utf32::icelake + { + [[nodiscard]] auto validate(const input_type input) noexcept -> result_error_input_type + { + return ::utf32::icelake::validate(input); + } + + [[nodiscard]] auto validate(const pointer_type input) noexcept -> result_error_input_type + { + return validate({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_for_latin(const input_type input) noexcept -> size_type + { + return ::utf32::icelake::length(input); + } + + [[nodiscard]] auto length_for_latin(const pointer_type input) noexcept -> size_type + { + return length_for_latin({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_for_utf8(const input_type input) noexcept -> size_type + { + const auto length = ::utf32::icelake::length(input); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length == ::utf32::icelake::length(input)); + + return length; + } + + [[nodiscard]] auto length_for_utf8(const pointer_type input) noexcept -> size_type + { + return length_for_utf8({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_for_utf16(const input_type input) noexcept -> size_type + { + const auto length = ::utf32::icelake::length(input); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length == ::utf32::icelake::length(input)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length == ::utf32::icelake::length(input)); + + return length; + } + + [[nodiscard]] auto length_for_utf16(const pointer_type input) noexcept -> size_type + { + return length_for_utf16({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_for_utf32(const input_type input) noexcept -> size_type + { + return input.size(); + } + + [[nodiscard]] auto length_for_utf32(const pointer_type input) noexcept -> size_type + { + return length_for_utf32({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_latin( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf32::icelake::write_latin(output, input); + } + + [[nodiscard]] auto write_latin( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_latin(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf32::icelake::write_latin(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_latin_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_latin_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf32::icelake::write_latin(output, input); + return {.output = result.output}; + } + + auto write_latin_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_latin_correct(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf8( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf32::icelake::write_utf8(output, input); + } + + [[nodiscard]] auto write_utf8( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf8(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf32::icelake::write_utf8(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf8_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf32::icelake::write_utf8(output, input); + return {.output = result.output}; + } + + auto write_utf8_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf8_correct(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf8( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf32::icelake::write_utf8(output, input); + } + + [[nodiscard]] auto write_utf8( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf8(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf32::icelake::write_utf8(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf8_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf32::icelake::write_utf8(output, input); + return {.output = result.output}; + } + + auto write_utf8_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf8_correct(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf16_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf32::icelake::write_utf16(output, input); + } + + [[nodiscard]] auto write_utf16_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf16_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf32::icelake::write_utf16(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf16_le_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_le_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf32::icelake::write_utf16(output, input); + return {.output = result.output}; + } + + auto write_utf16_le_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf16_le_correct(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf16_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf32::icelake::write_utf16(output, input); + } + + [[nodiscard]] auto write_utf16_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf16_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf32::icelake::write_utf16(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf16_be_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_be_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf32::icelake::write_utf16(output, input); + return {.output = result.output}; + } + + auto write_utf16_be_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf16_be_correct(output, {input, std::char_traits::length(input)}); + } + } + + [[nodiscard]] auto Icelake::encoding_of(const std::span input) noexcept -> EncodingType + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + + if (const auto bom = bom_of(input); + bom != EncodingType::UNKNOWN) + { + return bom; + } + + const auto input_length = input.size(); + + const auto it_input_begin = input.data(); + auto it_input_current = it_input_begin; + const auto it_input_end = it_input_begin + input_length; + + // utf8 + bool utf8 = true; + detail::utf8::icelake::avx512_utf8_checker checker{}; + // utf16 + bool utf16 = (input_length % 2) == 0; + bool utf16_ends_with_high = false; + // utf32 + bool utf32 = (input_length % 4) == 0; + + const auto do_check = [&](const data_type data) noexcept -> void + { + // utf8 + const auto offset = _mm512_set1_epi32(static_cast(0xffff'2000)); + // utf16 + const auto v_d800 = _mm512_set1_epi16(static_cast(0xd800)); + const auto v_0800 = _mm512_set1_epi16(0x0800); + const auto v_0400 = _mm512_set1_epi16(0x0400); + // utf32 + // const auto offset = _mm512_set1_epi32(static_cast(0xffff'2000)); + const auto standard_max = _mm512_set1_epi32(0x0010'ffff); + const auto standard_offset_max = _mm512_set1_epi32(static_cast(0xffff'f7ff)); + + // ::utf8::icelake::validate() + if (utf8) + { + if (not checker.check_data(data)) + { + if constexpr (Tail) + { + checker.check_eof(); + } + + if (checker.has_error()) + { + utf8 = false; + } + } + } + + // ::utf16::icelake::validate() + if (utf16) + { + const auto diff = _mm512_sub_epi16(data, v_d800); + if (const auto surrogates = _mm512_cmplt_epu16_mask(diff, v_0800); + surrogates) + { + const auto high_surrogates = _mm512_cmplt_epu16_mask(diff, v_0400); + const auto low_surrogates = surrogates ^ high_surrogates; + + if (((high_surrogates << 1) | +utf16_ends_with_high) != low_surrogates) + { + utf16 = false; + } + utf16_ends_with_high = (high_surrogates & 0x8000'0000) != 0; + } + } + + // ::utf32::icelake::validate() + if (utf32) + { + const auto value_offset = _mm512_add_epi32(data, offset); + + const auto outside_range = _mm512_cmpgt_epu32_mask(data, standard_max); + const auto surrogate_range = _mm512_cmpgt_epu32_mask(value_offset, standard_offset_max); + + if (outside_range | surrogate_range) + { + utf32 = false; + } + } + }; + + while (it_input_current + ::utf8::advance_of_utf8 <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + ::utf8::advance_of_utf8}; + #endif + + const auto data = _mm512_loadu_si512(it_input_current); + + do_check.operator()(data); + + it_input_current += ::utf8::advance_of_utf8; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(static_cast(remaining) < ::utf8::advance_of_utf8); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto mask = _bzhi_u64(~static_cast(0), static_cast(remaining)); + const auto data = _mm512_maskz_loadu_epi8(mask, it_input_current); + + do_check.operator()(data); + + it_input_current += remaining; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + + auto all_possible = std::to_underlying(EncodingType::UNKNOWN); + + if (utf8) + { + all_possible |= std::to_underlying(EncodingType::UTF8); + } + + if (utf16) + { + all_possible |= std::to_underlying(EncodingType::UTF16_LE); + } + + if (utf32) + { + all_possible |= std::to_underlying(EncodingType::UTF32_LE); + } + + return static_cast(all_possible); + } + + [[nodiscard]] auto Icelake::encoding_of(const std::span input) noexcept -> EncodingType + { + static_assert(sizeof(char) == sizeof(char8_t)); + + const auto* char8_string = GAL_PROMETHEUS_SEMANTIC_UNRESTRICTED_CHAR_POINTER_CAST(char8_t, input.data()); + return encoding_of({char8_string, input.size()}); + } +} + +#endif diff --git a/src/chars/icelake.hpp b/src/chars/icelake.hpp new file mode 100644 index 00000000..a636bc26 --- /dev/null +++ b/src/chars/icelake.hpp @@ -0,0 +1,8433 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + +#include + +namespace gal::prometheus::chars +{ + namespace latin::icelake + { + /** + * @brief Checks if there are all valid `ASCII` code point in the range of @c input. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate(input_type input) noexcept -> result_error_input_type; + + /** + * @brief Checks if there are all valid `ASCII` code point in the range of [@c input, @c input+std::char_traits::length(input)]. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate(pointer_type input) noexcept -> result_error_input_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_for_latin(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_for_latin(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf8(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf8(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf32(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf32(pointer_type input) noexcept -> size_type; + + // ======================================================= + // LATIN => UTF8_CHAR + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf8(input)); + + std::ignore = icelake::write_utf8(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_char({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf8(input) ==> input.size() + string.resize(length_for_utf8(input)); + + std::ignore = icelake::write_utf8_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_char_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf8(input)); + + std::ignore = icelake::write_utf8_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_char_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // LATIN => UTF8 + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf8(input)); + + std::ignore = icelake::write_utf8(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf8(input) ==> input.size() + string.resize(length_for_utf8(input)); + + std::ignore = icelake::write_utf8_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf8(input)); + + std::ignore = icelake::write_utf8_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // LATIN => UTF16_LE + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_le_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_le_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_le_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_pure(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_le_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_le_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_le_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_le_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_correct(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_le_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // LATIN => UTF16_BE + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_be_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_be_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_be_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_pure(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_be_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_be_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_be_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_be_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_correct(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_be_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // LATIN => UTF32 + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf32(input)); + + std::ignore = icelake::write_utf32(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf32({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf32(input) ==> input.size() + string.resize(length_for_utf32(input)); + + std::ignore = icelake::write_utf32_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf32_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf32(input)); + + std::ignore = icelake::write_utf32_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf32_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_correct({input, std::char_traits::length(input)}); + } + } + + namespace utf8_char::icelake + { + /** + * @brief Checks if there are all valid `UTF8` code point in the range of @c input. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate(input_type input) noexcept -> result_error_input_type; + + /** + * @brief Checks if there are all valid `UTF8` code point in the range of [@c input, @c input+std::char_traits::length(input)]. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate(pointer_type input) noexcept -> result_error_input_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_for_latin(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_for_latin(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf8(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf8(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf32(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf32(pointer_type input) noexcept -> size_type; + + // ======================================================= + // UTF8 => LATIN + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_latin(input)); + + std::ignore = icelake::write_latin(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin(const pointer_type input) noexcept -> StringType + { + return icelake::write_latin({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_latin(input) ==> input.size() + string.resize(length_for_latin(input)); + + std::ignore = icelake::write_latin_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure(const pointer_type input) noexcept -> StringType + { + return icelake::write_latin_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_latin(input)); + + std::ignore = icelake::write_latin_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct(const pointer_type input) noexcept -> StringType + { + return icelake::write_latin_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF8 => UTF16_LE + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_le_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_le_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_le_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_pure(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_le_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_le_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_le_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_le_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_correct(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_le_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF8 => UTF16_BE + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_be_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_be_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_be_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_pure(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_be_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_be_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_be_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_be_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_correct(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_be_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF8 => UTF32 + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf32(input)); + + std::ignore = icelake::write_utf32(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf32({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf32(input) ==> input.size() + string.resize(length_for_utf32(input)); + + std::ignore = icelake::write_utf32_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf32_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf32(input)); + + std::ignore = icelake::write_utf32_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf32_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF8_CHAR => UTF8 + + /** + * @brief Convert UTF8_CHAR string to UTF8 string up to the first invalid UTF8 code point. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Convert UTF8_CHAR string to UTF8 string up to the first invalid UTF8 code point. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Convert UTF8_CHAR string to UTF8 string up to the first invalid UTF8 code point. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf8(input) ==> input.size() + string.resize(length_for_utf8(input)); + + std::ignore = icelake::write_utf8(string.data(), input); + + return string; + } + + /** + * @brief Convert UTF8_CHAR string to UTF8 string up to the first invalid UTF8 code point. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8({input, std::char_traits::length(input)}); + } + + /** + * @brief Convert UTF8_CHAR string to UTF8 string up to the first invalid UTF8 code point. + */ + [[nodiscard]] inline auto write_utf8(const input_type input) noexcept -> std::basic_string + { + return icelake::write_utf8>(input); + } + + /** + * @brief Convert UTF8_CHAR string to UTF8 string up to the first invalid UTF8 code point. + */ + [[nodiscard]] inline auto write_utf8(const pointer_type input) noexcept -> std::basic_string + { + return icelake::write_utf8({input, std::char_traits::length(input)}); + } + } + + namespace utf8::icelake + { + /** + * @brief Checks if there are all valid `UTF8` code point in the range of @c input. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate(input_type input) noexcept -> result_error_input_type; + + /** + * @brief Checks if there are all valid `UTF8` code point in the range of [@c input, @c input+std::char_traits::length(input)]. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate(pointer_type input) noexcept -> result_error_input_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_for_latin(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_for_latin(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf8(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf8(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf32(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf32(pointer_type input) noexcept -> size_type; + + // ======================================================= + // UTF8 => LATIN + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_latin(input)); + + std::ignore = icelake::write_latin(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin(const pointer_type input) noexcept -> StringType + { + return icelake::write_latin({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_latin(input) ==> input.size() + string.resize(length_for_latin(input)); + + std::ignore = icelake::write_latin_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure(const pointer_type input) noexcept -> StringType + { + return icelake::write_latin_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_latin(input)); + + std::ignore = icelake::write_latin_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct(const pointer_type input) noexcept -> StringType + { + return icelake::write_latin_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF8 => UTF16_LE + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_le_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_le_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_le_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_pure(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_le_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_le_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_le_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_le_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_correct(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_le_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF8 => UTF16_BE + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_be_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_be_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_be_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_pure(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_be_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_be_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_be_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_be_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_correct(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_be_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF8 => UTF32 + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf32(input)); + + std::ignore = icelake::write_utf32(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf32({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf32(input) ==> input.size() + string.resize(length_for_utf32(input)); + + std::ignore = icelake::write_utf32_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf32_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf32(input)); + + std::ignore = icelake::write_utf32_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf32_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF8 => UTF8_CHAR + + /** + * @brief Convert UTF8 string to UTF8_CHAR string up to the first invalid UTF8 code point. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Convert UTF8 string to UTF8_CHAR string up to the first invalid UTF8 code point. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Convert UTF8 string to UTF8_CHAR string up to the first invalid UTF8 code point. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf8(input)); + + std::ignore = icelake::write_utf8(string.data(), input); + + return string; + } + + /** + * @brief Convert UTF8 string to UTF8_CHAR string up to the first invalid UTF8 code point. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8({input, std::char_traits::length(input)}); + } + + /** + * @brief Convert UTF8 string to UTF8_CHAR string up to the first invalid UTF8 code point. + */ + [[nodiscard]] inline auto write_utf8(const input_type input) noexcept -> std::basic_string + { + return icelake::write_utf8>(input); + } + + /** + * @brief Convert UTF8 string to UTF8_CHAR string up to the first invalid UTF8 code point. + */ + [[nodiscard]] inline auto write_utf8(const pointer_type input) noexcept -> std::basic_string + { + return icelake::write_utf8({input, std::char_traits::length(input)}); + } + } + + namespace utf16::icelake + { + /** + * @brief Checks if there are all valid `UTF16 (little-endian)` code point in the range of @c input. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate_le(input_type input) noexcept -> result_error_input_type; + + /** + * @brief Checks if there are all valid `UTF16 (little-endian)` code point in the range of [@c input, @c input+std::char_traits::length(input)]. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate_le(pointer_type input) noexcept -> result_error_input_type; + + /** + * @brief Checks if there are all valid `UTF16 (big-endian)` code point in the range of @c input. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate_be(input_type input) noexcept -> result_error_input_type; + + /** + * @brief Checks if there are all valid `UTF16 (big-endian)` code point in the range of [@c input, @c input+std::char_traits::length(input)]. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate_be(pointer_type input) noexcept -> result_error_input_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_le_for_latin(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_le_for_latin(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_be_for_latin(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_be_for_latin(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_le_for_utf8(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_le_for_utf8(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_be_for_utf8(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_be_for_utf8(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_le_for_utf32(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_le_for_utf32(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_be_for_utf32(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_be_for_utf32(pointer_type input) noexcept -> size_type; + + // ======================================================= + // UTF16 => LATIN + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_le_for_latin(input)); + + std::ignore = icelake::write_latin_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_le(const pointer_type input) noexcept -> StringType + { + return icelake::write_latin_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_be_for_latin(input)); + + std::ignore = icelake::write_latin_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_be(const pointer_type input) noexcept -> StringType + { + return icelake::write_latin_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure_le(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_le_for_latin(input) ==> input.size() + string.resize(length_le_for_latin(input)); + + std::ignore = icelake::write_latin_pure_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure_le(const pointer_type input) noexcept -> StringType + { + return icelake::write_latin_pure_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_pure_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_pure_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure_be(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_be_for_latin(input) ==> input.size() + string.resize(length_be_for_latin(input)); + + std::ignore = icelake::write_latin_pure_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure_be(const pointer_type input) noexcept -> StringType + { + return icelake::write_latin_pure_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_pure_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_pure_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_le_for_latin(input)); + + std::ignore = icelake::write_latin_correct_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct_le(const pointer_type input) noexcept -> StringType + { + return icelake::write_latin_correct_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_correct_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_correct_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_be_for_latin(input)); + + std::ignore = icelake::write_latin_correct_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct_be(const pointer_type input) noexcept -> StringType + { + return icelake::write_latin_correct_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_correct_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_correct_be({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF16 => UTF8_CHAR + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_le_for_utf8(input)); + + std::ignore = icelake::write_utf8_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_le(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_char_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_be_for_utf8(input)); + + std::ignore = icelake::write_utf8_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_be(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_char_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure_le(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_le_for_utf8(input) ==> input.size() + string.resize(length_le_for_utf8(input)); + + std::ignore = icelake::write_utf8_pure_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure_le(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_pure_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_pure_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_pure_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_pure_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_char_pure_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure_be(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_be_for_utf8(input) ==> input.size() + string.resize(length_be_for_utf8(input)); + + std::ignore = icelake::write_utf8_pure_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure_be(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_pure_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_pure_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_pure_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_pure_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_char_pure_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_le_for_utf8(input)); + + std::ignore = icelake::write_utf8_correct_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct_le(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_correct_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_correct_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_correct_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_correct_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_char_correct_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_be_for_utf8(input)); + + std::ignore = icelake::write_utf8_correct_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct_be(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_correct_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_correct_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_correct_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_correct_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_char_correct_be({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF16 => UTF8 + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_le_for_utf8(input)); + + std::ignore = icelake::write_utf8_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_le(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_be_for_utf8(input)); + + std::ignore = icelake::write_utf8_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_be(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure_le(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_le_for_utf8(input) ==> input.size() + string.resize(length_le_for_utf8(input)); + + std::ignore = icelake::write_utf8_pure_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure_le(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_pure_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_pure_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_pure_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_pure_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_pure_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure_be(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_be_for_utf8(input) ==> input.size() + string.resize(length_be_for_utf8(input)); + + std::ignore = icelake::write_utf8_pure_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure_be(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_pure_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_pure_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_pure_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_pure_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_pure_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_le_for_utf8(input)); + + std::ignore = icelake::write_utf8_correct_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct_le(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_correct_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_correct_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_correct_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_correct_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_correct_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_be_for_utf8(input)); + + std::ignore = icelake::write_utf8_correct_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct_be(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_correct_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_correct_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_correct_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_correct_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_correct_be({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF16 => UTF32 + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_le_for_utf32(input)); + + std::ignore = icelake::write_utf32_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_le(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf32_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_be_for_utf32(input)); + + std::ignore = icelake::write_utf32_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_be(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf32_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure_le(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_le_for_utf32(input) ==> input.size() + string.resize(length_le_for_utf32(input)); + + std::ignore = icelake::write_utf32_pure_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure_le(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf32_pure_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_pure_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_pure_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure_be(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_be_for_utf32(input) ==> input.size() + string.resize(length_be_for_utf32(input)); + + std::ignore = icelake::write_utf32_pure_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure_be(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf32_pure_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_pure_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_pure_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_le_for_utf32(input)); + + std::ignore = icelake::write_utf32_correct_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct_le(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf32_correct_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_correct_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_correct_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_be_for_utf32(input)); + + std::ignore = icelake::write_utf32_correct_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct_be(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf32_correct_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_correct_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf32_correct_be({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF16_LE => UTF16_LE + // UTF16_BE => UTF16_BE + + /** + * @brief Convert UTF16_LE string to UTF16_BE string up to the first invalid UTF16_LE code point. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Convert UTF16_LE string to UTF16_BE string up to the first invalid UTF16_LE code point. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Convert UTF16_LE string to UTF16_BE string up to the first invalid UTF16_LE code point. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_to_be(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_le(string.data(), input); + + return string; + } + + /** + * @brief Convert UTF16_LE string to UTF16_BE string up to the first invalid UTF16_LE code point. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_to_be(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_le_to_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Convert UTF16_LE string to UTF16_BE string up to the first invalid UTF16_LE code point. + */ + [[nodiscard]] inline auto write_utf16_le_to_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le_to_be::value_type>>(input); + } + + /** + * @brief Convert UTF16_LE string to UTF16_BE string up to the first invalid UTF16_LE code point. + */ + [[nodiscard]] inline auto write_utf16_le_to_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le_to_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Convert UTF16_BE string to UTF16_LE string up to the first invalid UTF16_BE code point. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Convert UTF16_BE string to UTF16_LE string up to the first invalid UTF16_BE code point. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Convert UTF16_BE string to UTF16_LE string up to the first invalid UTF16_BE code point. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_to_le(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_be(string.data(), input); + + return string; + } + + /** + * @brief Convert UTF16_BE string to UTF16_LE string up to the first invalid UTF16_BE code point. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_to_le(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_be_to_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Convert UTF16_BE string to UTF16_LE string up to the first invalid UTF16_BE code point. + */ + [[nodiscard]] inline auto write_utf16_be_to_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be_to_le::value_type>>(input); + } + + /** + * @brief Convert UTF16_BE string to UTF16_LE string up to the first invalid UTF16_BE code point. + */ + [[nodiscard]] inline auto write_utf16_be_to_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be_to_le({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF16_LE => UTF16_BE + // UTF16_BE => UTF16_LE + + /** + * @brief Convert UTF16_LE string to UTF16_BE string (or vice versa), assuming the input string is valid. + */ + auto flip( + output_type_of::pointer output, + input_type input + ) noexcept -> void; + + /** + * @brief Convert UTF16_LE string to UTF16_BE string (or vice versa), assuming the input string is valid. + */ + auto flip( + output_type_of::pointer output, + pointer_type input + ) noexcept -> void; + + /** + * @brief Convert UTF16_LE string to UTF16_BE string (or vice versa), assuming the input string is valid. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto flip(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + icelake::flip(string.data(), input); + + return string; + } + + /** + * @brief Convert UTF16_LE string to UTF16_BE string (or vice versa), assuming the input string is valid. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto flip(const pointer_type input) noexcept -> StringType + { + return icelake::flip({input, std::char_traits::length(input)}); + } + + /** + * @brief Convert UTF16_LE string to UTF16_BE string (or vice versa), assuming the input string is valid. + */ + [[nodiscard]] inline auto flip(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::flip::value_type>>(input); + } + + /** + * @brief Convert UTF16_LE string to UTF16_BE string (or vice versa), assuming the input string is valid. + */ + [[nodiscard]] inline auto flip(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::flip({input, std::char_traits::length(input)}); + } + } + + namespace utf32::icelake + { + /** + * @brief Checks if there are all valid `UTF32` code point in the range of @c input. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate(input_type input) noexcept -> result_error_input_type; + + /** + * @brief Checks if there are all valid `UTF32` code point in the range of [@c input, @c input+std::char_traits::length(input)]. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate(pointer_type input) noexcept -> result_error_input_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_for_latin(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_for_latin(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf8(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf8(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf32(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf32(pointer_type input) noexcept -> size_type; + + // ======================================================= + // UTF32 => LATIN + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_latin(input)); + + std::ignore = icelake::write_latin(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin(const pointer_type input) noexcept -> StringType + { + return icelake::write_latin({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_latin(input) ==> input.size() + string.resize(length_for_latin(input)); + + std::ignore = icelake::write_latin_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure(const pointer_type input) noexcept -> StringType + { + return icelake::write_latin_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_latin(input)); + + std::ignore = icelake::write_latin_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct(const pointer_type input) noexcept -> StringType + { + return icelake::write_latin_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_latin_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF32 => UTF8_CHAR + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf8(input)); + + std::ignore = icelake::write_utf8(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_char({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf8(input) ==> input.size() + string.resize(length_for_utf8(input)); + + std::ignore = icelake::write_utf8_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_char_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf8(input)); + + std::ignore = icelake::write_utf8_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_char_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF32 => UTF8 + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf8(input)); + + std::ignore = icelake::write_utf8(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf8(input) ==> input.size() + string.resize(length_for_utf8(input)); + + std::ignore = icelake::write_utf8_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf8(input)); + + std::ignore = icelake::write_utf8_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf8_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf8_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF32 => UTF16_LE + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_le_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_le_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_le_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_pure(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_le_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_le_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_le_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_le_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_correct(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_le_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_le_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF32 => UTF16_BE + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_be_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_be_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_be_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_pure(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_be_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_be_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_be_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = icelake::write_utf16_be_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_correct(const pointer_type input) noexcept -> StringType + { + return icelake::write_utf16_be_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return icelake::write_utf16_be_correct({input, std::char_traits::length(input)}); + } + } + + class Icelake + { + public: + // =================================================== + // encoding + + [[nodiscard]] static auto encoding_of(std::span input) noexcept -> EncodingType; + + [[nodiscard]] static auto encoding_of(std::span input) noexcept -> EncodingType; + + // // =================================================== + // // validate + // + // template + // [[nodiscard]] constexpr static auto validate( + // const typename input_type_of::const_pointer current, + // const typename input_type_of::const_pointer end + // ) noexcept -> std::pair + // { + // return Scalar::validate(current, end); + // } + // + // private: + template + [[nodiscard]] constexpr static auto do_validate(const Input input) noexcept -> result_error_input_type + { + if constexpr (InputType == CharsType::LATIN) + { + return latin::icelake::validate(input); + } + else if constexpr (InputType == CharsType::UTF8_CHAR) + { + return utf8_char::icelake::validate(input); + } + else if constexpr (InputType == CharsType::UTF8) + { + return utf8::icelake::validate(input); + } + else if constexpr (InputType == CharsType::UTF16_LE) + { + return utf16::icelake::validate_le(input); + } + else if constexpr (InputType == CharsType::UTF16_BE) + { + return utf16::icelake::validate_be(input); + } + else if constexpr (InputType == CharsType::UTF32) + { + return utf32::icelake::validate(input); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + public: + template + [[nodiscard]] constexpr static auto validate(const input_type_of input) noexcept -> result_error_input_type + { + return Icelake::do_validate(input); + } + + template + [[nodiscard]] constexpr static auto validate(const typename input_type_of::const_pointer input) noexcept -> result_error_input_type + { + return Icelake::do_validate(input); + } + + // =================================================== + // length + + private: + template + [[nodiscard]] constexpr static auto do_length(const Input input) noexcept -> typename input_type_of::size_type + { + if constexpr (InputType == CharsType::LATIN) + { + using namespace latin; + + if constexpr (OutputType == CharsType::LATIN) + { + return icelake::length_for_latin(input); + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + return icelake::length_for_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE or OutputType == CharsType::UTF16_BE or OutputType == CharsType::UTF16) + { + return icelake::length_for_utf16(input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + return icelake::length_for_utf32(input); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF8_CHAR) + { + using namespace utf8_char; + + if constexpr (OutputType == CharsType::LATIN) + { + return icelake::length_for_latin(input); + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + return icelake::length_for_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE or OutputType == CharsType::UTF16_BE or OutputType == CharsType::UTF16) + { + return icelake::length_for_utf16(input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + return icelake::length_for_utf32(input); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF8) + { + using namespace utf8; + + if constexpr (OutputType == CharsType::LATIN) + { + return icelake::length_for_latin(input); + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + return icelake::length_for_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE or OutputType == CharsType::UTF16_BE or OutputType == CharsType::UTF16) + { + return icelake::length_for_utf16(input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + return icelake::length_for_utf32(input); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF16_LE) + { + using namespace utf16; + + if constexpr (OutputType == CharsType::LATIN) + { + return icelake::length_le_for_latin(input); + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + return icelake::length_le_for_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE or OutputType == CharsType::UTF16_BE or OutputType == CharsType::UTF16) + { + return icelake::length_for_utf16(input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + return icelake::length_le_for_utf32(input); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF16_BE) + { + using namespace utf16; + + if constexpr (OutputType == CharsType::LATIN) + { + return icelake::length_be_for_latin(input); + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + return icelake::length_be_for_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE or OutputType == CharsType::UTF16_BE or OutputType == CharsType::UTF16) + { + return icelake::length_for_utf16(input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + return icelake::length_be_for_utf32(input); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF32) + { + using namespace utf32; + + if constexpr (OutputType == CharsType::LATIN) + { + return icelake::length_for_latin(input); + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + return icelake::length_for_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE or OutputType == CharsType::UTF16_BE or OutputType == CharsType::UTF16) + { + return icelake::length_for_utf16(input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + return icelake::length_for_utf32(input); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + public: + template + [[nodiscard]] constexpr static auto length(const input_type_of input) noexcept -> typename input_type_of::size_type + { + return Icelake::do_length(input); + } + + template + [[nodiscard]] constexpr static auto length(const typename input_type_of::const_pointer input) noexcept -> typename input_type_of::size_type + { + return Icelake::do_length(input); + } + + // =================================================== + // write + + // template + // [[nodiscard]] constexpr static auto convert( + // typename output_type_of::pointer& output, + // const typename input_type_of::const_pointer current, + // const typename input_type_of::const_pointer end + // ) noexcept -> std::pair + // { + // return Scalar::convert(output, current, end); + // } + + private: + template + requires ( + std::is_convertible_v> or + std::is_convertible_v::const_pointer> + ) + [[nodiscard]] constexpr static auto do_convert( + const typename output_type_of::pointer output, + const Input input + ) noexcept -> auto + { + if constexpr (InputType == CharsType::LATIN) + { + using namespace latin; + + if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return icelake::write_utf8_pure(output, input); + } + else if constexpr (Correct) + { + return icelake::write_utf8_correct(output, input); + } + else + { + return icelake::write_utf8(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return icelake::write_utf16_le_pure(output, input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_le_correct(output, input); + } + else + { + return icelake::write_utf16_le(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return icelake::write_utf16_be_pure(output, input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_be_correct(output, input); + } + else + { + return icelake::write_utf16_be(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return icelake::write_utf32_pure(output, input); + } + else if constexpr (Correct) + { + return icelake::write_utf32_correct(output, input); + } + else + { + return icelake::write_utf32(output, input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF8_CHAR) + { + using namespace utf8_char; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return icelake::write_latin_pure(output, input); + } + else if constexpr (Correct) + { + return icelake::write_latin_correct(output, input); + } + else + { + return icelake::write_latin(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF8) + { + return icelake::write_utf8(output, input); + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return icelake::write_utf16_le_pure(output, input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_le_correct(output, input); + } + else + { + return icelake::write_utf16_le(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return icelake::write_utf16_be_pure(output, input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_be_correct(output, input); + } + else + { + return icelake::write_utf16_be(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return icelake::write_utf32_pure(output, input); + } + else if constexpr (Correct) + { + return icelake::write_utf32_correct(output, input); + } + else + { + return icelake::write_utf32(output, input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF8) + { + using namespace utf8; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return icelake::write_latin_pure(output, input); + } + else if constexpr (Correct) + { + return icelake::write_latin_correct(output, input); + } + else + { + return icelake::write_latin(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR) + { + return icelake::write_utf8(output, input); + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return icelake::write_utf16_le_pure(output, input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_le_correct(output, input); + } + else + { + return icelake::write_utf16_le(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return icelake::write_utf16_be_pure(output, input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_be_correct(output, input); + } + else + { + return icelake::write_utf16_be(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return icelake::write_utf32_pure(output, input); + } + else if constexpr (Correct) + { + return icelake::write_utf32_correct(output, input); + } + else + { + return icelake::write_utf32(output, input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF16_LE) + { + using namespace utf16; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return icelake::write_latin_pure_le(output, input); + } + else if constexpr (Correct) + { + return icelake::write_latin_correct_le(output, input); + } + else + { + return icelake::write_latin_le(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return icelake::write_utf8_pure_le(output, input); + } + else if constexpr (Correct) + { + return icelake::write_utf8_correct_le(output, input); + } + else + { + return icelake::write_utf8_le(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + return icelake::write_utf16_le(output, input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return icelake::write_utf32_pure_le(output, input); + } + else if constexpr (Correct) + { + return icelake::write_utf32_correct_le(output, input); + } + else + { + return icelake::write_utf32_le(output, input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF16_BE) + { + using namespace utf16; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return icelake::write_latin_pure_be(output, input); + } + else if constexpr (Correct) + { + return icelake::write_latin_correct_be(output, input); + } + else + { + return icelake::write_latin_be(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return icelake::write_utf8_pure_be(output, input); + } + else if constexpr (Correct) + { + return icelake::write_utf8_correct_be(output, input); + } + else + { + return icelake::write_utf8_be(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + return icelake::write_utf16_be(output, input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return icelake::write_utf32_pure_be(output, input); + } + else if constexpr (Correct) + { + return icelake::write_utf32_correct_be(output, input); + } + else + { + return icelake::write_utf32_be(output, input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF32) + { + using namespace utf32; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return icelake::write_latin_pure(output, input); + } + else if constexpr (Correct) + { + return icelake::write_latin_correct(output, input); + } + else + { + return icelake::write_latin(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return icelake::write_utf8_pure(output, input); + } + else if constexpr (Correct) + { + return icelake::write_utf8_correct(output, input); + } + else + { + return icelake::write_utf8(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return icelake::write_utf16_le_pure(output, input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_le_correct(output, input); + } + else + { + return icelake::write_utf16_le(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return icelake::write_utf16_be_pure(output, input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_be_correct(output, input); + } + else + { + return icelake::write_utf16_be(output, input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + public: + template + [[nodiscard]] constexpr static auto convert( + const typename output_type_of::pointer output, + const input_type_of input + ) noexcept -> auto + { + return Icelake::do_convert(output, input); + } + + template + [[nodiscard]] constexpr static auto convert( + const typename output_type_of::pointer output, + const typename input_type_of::const_pointer input + ) noexcept -> auto + { + return Icelake::do_convert(output, input); + } + + private: + template + [[nodiscard]] constexpr static auto do_convert(const Input input) noexcept -> StringType + { + if constexpr (InputType == CharsType::LATIN) + { + using namespace latin; + + if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return icelake::write_utf8_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf8_correct(input); + } + else + { + return icelake::write_utf8(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return icelake::write_utf16_le_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_le_correct(input); + } + else + { + return icelake::write_utf16_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return icelake::write_utf16_be_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_be_correct(input); + } + else + { + return icelake::write_utf16_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return icelake::write_utf32_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf32_correct(input); + } + else + { + return icelake::write_utf32(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF8_CHAR) + { + using namespace utf8_char; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return icelake::write_latin_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_latin_correct(input); + } + else + { + return icelake::write_latin(input); + } + } + else if constexpr (OutputType == CharsType::UTF8) + { + return icelake::write_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return icelake::write_utf16_le_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_le_correct(input); + } + else + { + return icelake::write_utf16_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return icelake::write_utf16_be_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_be_correct(input); + } + else + { + return icelake::write_utf16_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return icelake::write_utf32_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf32_correct(input); + } + else + { + return icelake::write_utf32(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF8) + { + using namespace utf8; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return icelake::write_latin_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_latin_correct(input); + } + else + { + return icelake::write_latin(input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR) + { + return icelake::write_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return icelake::write_utf16_le_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_le_correct(input); + } + else + { + return icelake::write_utf16_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return icelake::write_utf16_be_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_be_correct(input); + } + else + { + return icelake::write_utf16_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return icelake::write_utf32_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf32_correct(input); + } + else + { + return icelake::write_utf32(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF16_LE) + { + using namespace utf16; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return icelake::write_latin_pure_le(input); + } + else if constexpr (Correct) + { + return icelake::write_latin_correct_le(input); + } + else + { + return icelake::write_latin_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return icelake::write_utf8_pure_le(input); + } + else if constexpr (Correct) + { + return icelake::write_utf8_correct_le(input); + } + else + { + return icelake::write_utf8_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return icelake::write_utf32_pure_le(input); + } + else if constexpr (Correct) + { + return icelake::write_utf32_correct_le(input); + } + else + { + return icelake::write_utf32_le(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF16_BE) + { + using namespace utf16; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return icelake::write_latin_pure_be(input); + } + else if constexpr (Correct) + { + return icelake::write_latin_correct_be(input); + } + else + { + return icelake::write_latin_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return icelake::write_utf8_pure_be(input); + } + else if constexpr (Correct) + { + return icelake::write_utf8_correct_be(input); + } + else + { + return icelake::write_utf8_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return icelake::write_utf32_pure_be(input); + } + else if constexpr (Correct) + { + return icelake::write_utf32_correct_be(input); + } + else + { + return icelake::write_utf32_be(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF32) + { + using namespace utf32; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return icelake::write_latin_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_latin_correct(input); + } + else + { + return icelake::write_latin(input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return icelake::write_utf8_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf8_correct(input); + } + else + { + return icelake::write_utf8(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return icelake::write_utf16_le_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_le_correct(input); + } + else + { + return icelake::write_utf16_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return icelake::write_utf16_be_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_be_correct(input); + } + else + { + return icelake::write_utf16_be(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + public: + template + [[nodiscard]] constexpr static auto convert(const input_type_of input) noexcept -> StringType + { + return Icelake::do_convert(input); + } + + template + [[nodiscard]] constexpr static auto convert(const typename input_type_of::const_pointer input) noexcept -> StringType + { + return Icelake::do_convert(input); + } + + private: + template + [[nodiscard]] constexpr static auto do_convert(const Input input) noexcept -> std::basic_string::value_type> + { + if constexpr (InputType == CharsType::LATIN) + { + using namespace latin; + + if constexpr (OutputType == CharsType::UTF8_CHAR) + { + if constexpr (Pure) + { + return icelake::write_utf8_char_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf8_char_correct(input); + } + else + { + return icelake::write_utf8_char(input); + } + } + else if constexpr (OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return icelake::write_utf8_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf8_correct(input); + } + else + { + return icelake::write_utf8(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return icelake::write_utf16_le_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_le_correct(input); + } + else + { + return icelake::write_utf16_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return icelake::write_utf16_be_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_be_correct(input); + } + else + { + return icelake::write_utf16_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return icelake::write_utf32_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf32_correct(input); + } + else + { + return icelake::write_utf32(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF8_CHAR) + { + using namespace utf8_char; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return icelake::write_latin_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_latin_correct(input); + } + else + { + return icelake::write_latin(input); + } + } + else if constexpr (OutputType == CharsType::UTF8) + { + return icelake::write_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return icelake::write_utf16_le_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_le_correct(input); + } + else + { + return icelake::write_utf16_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return icelake::write_utf16_be_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_be_correct(input); + } + else + { + return icelake::write_utf16_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return icelake::write_utf32_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf32_correct(input); + } + else + { + return icelake::write_utf32(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF8) + { + using namespace utf8; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return icelake::write_latin_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_latin_correct(input); + } + else + { + return icelake::write_latin(input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR) + { + return icelake::write_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return icelake::write_utf16_le_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_le_correct(input); + } + else + { + return icelake::write_utf16_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return icelake::write_utf16_be_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_be_correct(input); + } + else + { + return icelake::write_utf16_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return icelake::write_utf32_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf32_correct(input); + } + else + { + return icelake::write_utf32(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF16_LE) + { + using namespace utf16; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return icelake::write_latin_pure_le(input); + } + else if constexpr (Correct) + { + return icelake::write_latin_correct_le(input); + } + else + { + return icelake::write_latin_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR) + { + if constexpr (Pure) + { + return icelake::write_utf8_char_pure_le(input); + } + else if constexpr (Correct) + { + return icelake::write_utf8_char_correct_le(input); + } + else + { + return icelake::write_utf8_char_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return icelake::write_utf8_pure_le(input); + } + else if constexpr (Correct) + { + return icelake::write_utf8_correct_le(input); + } + else + { + return icelake::write_utf8_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + return icelake::write_utf16_le_to_be(input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return icelake::write_utf32_pure_le(input); + } + else if constexpr (Correct) + { + return icelake::write_utf32_correct_le(input); + } + else + { + return icelake::write_utf32_le(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF16_BE) + { + using namespace utf16; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return icelake::write_latin_pure_be(input); + } + else if constexpr (Correct) + { + return icelake::write_latin_correct_be(input); + } + else + { + return icelake::write_latin_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR) + { + if constexpr (Pure) + { + return icelake::write_utf8_char_pure_be(input); + } + else if constexpr (Correct) + { + return icelake::write_utf8_char_correct_be(input); + } + else + { + return icelake::write_utf8_char_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return icelake::write_utf8_pure_be(input); + } + else if constexpr (Correct) + { + return icelake::write_utf8_correct_be(input); + } + else + { + return icelake::write_utf8_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + return icelake::write_utf16_be_to_le(input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return icelake::write_utf32_pure_be(input); + } + else if constexpr (Correct) + { + return icelake::write_utf32_correct_be(input); + } + else + { + return icelake::write_utf32_be(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF32) + { + using namespace utf32; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return icelake::write_latin_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_latin_correct(input); + } + else + { + return icelake::write_latin(input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR) + { + if constexpr (Pure) + { + return icelake::write_utf8_char_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf8_char_correct(input); + } + else + { + return icelake::write_utf8_char(input); + } + } + else if constexpr (OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return icelake::write_utf8_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf8_correct(input); + } + else + { + return icelake::write_utf8(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return icelake::write_utf16_le_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_le_correct(input); + } + else + { + return icelake::write_utf16_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return icelake::write_utf16_be_pure(input); + } + else if constexpr (Correct) + { + return icelake::write_utf16_be_correct(input); + } + else + { + return icelake::write_utf16_be(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + public: + template + [[nodiscard]] constexpr static auto convert(const input_type_of input) noexcept -> std::basic_string::value_type> + { + return Icelake::do_convert(input); + } + + template + [[nodiscard]] constexpr static auto convert(const typename input_type_of::const_pointer input) noexcept -> std::basic_string::value_type> + { + return Icelake::do_convert(input); + } + + private: + template + constexpr static auto do_flip( + const output_type_of::pointer output, + const Input input + ) noexcept -> void + { + using namespace utf16; + + icelake::flip(output, input); + } + + public: + constexpr static auto flip( + const output_type_of::pointer output, + const input_type_of input + ) noexcept -> void + { + return Icelake::do_flip(output, input); + } + + constexpr static auto flip( + const output_type_of::pointer output, + const input_type_of::const_pointer input + ) noexcept -> void + { + return Icelake::do_flip(output, input); + } + + private: + template + [[nodiscard]] constexpr static auto do_flip(const Input input) noexcept -> StringType + { + using namespace utf16; + + return icelake::flip(input); + } + + public: + template + [[nodiscard]] constexpr static auto flip(const input_type_of input) noexcept -> StringType + { + return Icelake::do_flip(input); + } + + template + [[nodiscard]] constexpr static auto flip(const input_type_of::const_pointer input) noexcept -> StringType + { + return Icelake::do_flip(input); + } + + private: + template + [[nodiscard]] constexpr static auto do_flip(const Input input) noexcept -> std::basic_string::value_type> + { + using namespace utf16; + + return icelake::flip(input); + } + + public: + [[nodiscard]] constexpr static auto flip(const input_type_of input) noexcept -> std::basic_string::value_type> + { + return Icelake::do_flip(input); + } + + [[nodiscard]] constexpr static auto flip(const input_type_of::const_pointer input) noexcept -> std::basic_string::value_type> + { + return Icelake::do_flip(input); + } + }; +} + +#endif diff --git a/src/chars/icelake.ixx b/src/chars/icelake.ixx deleted file mode 100644 index aa4544f5..00000000 --- a/src/chars/icelake.ixx +++ /dev/null @@ -1,201 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#if __has_include() -#include -#endif -#if __has_include() -#include -#endif -#include - -export module gal.prometheus.chars:icelake; - -import std; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -import :encoding; -export import :icelake.ascii; -export import :icelake.utf8; -export import :icelake.utf16; -export import :icelake.utf32; - -#else -#if __has_include() -#include -#endif -#if __has_include() -#include -#endif - -#include -#include -#include -#include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -#endif - -// ReSharper disable once CppRedundantNamespaceDefinition -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::chars) -{ - template<> - class Encoding<"icelake"> - { - public: - [[nodiscard]] constexpr static auto encoding_of(const std::span input) noexcept -> EncodingType - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - if (const auto bom = bom_of(input); bom != EncodingType::UNKNOWN) - { - return bom; - } - - if (const auto input_length = input.size(); - (input_length % 2) == 0) - { - const auto it_input_begin = input.data(); - auto it_input_current = it_input_begin; - const auto it_input_end = it_input_begin + input_length; - - avx512_utf8_checker checker{}; - auto current_max = _mm512_setzero_si512(); - - for (; it_input_current + 64 <= it_input_end; it_input_current += 64) - { - const auto in = _mm512_loadu_si512(it_input_current); - const auto diff = _mm512_sub_epi16(in, _mm512_set1_epi16(static_cast(0xd800))); - if (const auto surrogates = _mm512_cmplt_epu16_mask(diff, _mm512_set1_epi16(0x0800)); - surrogates) - { - // Can still be either UTF-16LE or UTF-32 depending on the positions of the surrogates - // To be valid UTF-32, a surrogate cannot be in the two most significant bytes of any 32-bit word. - // On the other hand, to be valid UTF-16LE, at least one surrogate must be in the two most significant bytes of a 32-bit word since they always come in pairs in UTF-16LE. - // Note that we always proceed in multiple of 4 before this point so there is no offset in 32-bit code units. - - if ((surrogates & 0xaaaa'aaaa) != 0) - { - const auto high_surrogates = _mm512_cmplt_epu16_mask(diff, _mm512_set1_epi16(0x0400)); - const auto low_surrogates = surrogates ^ high_surrogates; - // high must be followed by low - if ((high_surrogates << 1) != low_surrogates) - { - return EncodingType::UNKNOWN; - } - - if ((high_surrogates & 0x8000'0000) != 0) - { - // advance only by 31 code units so that we start with the high surrogate on the next round. - it_input_current += 31 * sizeof(char16_t); - } - else - { - it_input_current += 32 * sizeof(char16_t); - } - - if (Simd<"icelake.utf16">::validate( - { - GAL_PROMETHEUS_SEMANTIC_UNRESTRICTED_CHAR_POINTER_CAST(Scalar<"utf16">::char_type, it_input_current), - static_cast::size_type>((it_input_end - it_input_current) / sizeof(Scalar<"utf16">::char_type)) - } - )) - { - return EncodingType::UTF16_LE; - } - - return EncodingType::UNKNOWN; - } - - if ((input_length % 4) == 0 and - Simd<"icelake.utf32">::validate( - { - GAL_PROMETHEUS_SEMANTIC_UNRESTRICTED_CHAR_POINTER_CAST(Scalar<"utf32">::char_type, it_input_current), - static_cast::size_type>((it_input_end - it_input_current) / sizeof(Scalar<"utf32">::char_type)) - } - )) - { - return EncodingType::UTF32_LE; - } - - return EncodingType::UNKNOWN; - } - - // If no surrogate, validate under other encodings as well - - // UTF-32 validation - current_max = _mm512_max_epu32(in, current_max); - // UTF-8 validation - checker.check_input(in); - } - - // Check which encodings are possible - auto all_possible = std::to_underlying(EncodingType::UNKNOWN); - const auto remaining = it_input_end - it_input_current; - - // utf8 - { - if (remaining != 0) - { - const auto in = _mm512_maskz_loadu_epi8((__mmask64{1} << remaining) - 1, it_input_current); - checker.check_input(in); - } - checker.check_eof(); - if (not checker.has_error()) - { - all_possible |= std::to_underlying(EncodingType::UTF8); - } - } - // utf16 - { - if (Scalar<"utf16">::validate( - { - GAL_PROMETHEUS_SEMANTIC_UNRESTRICTED_CHAR_POINTER_CAST(Scalar<"utf16">::char_type, it_input_current), - static_cast::size_type>(remaining / sizeof(Scalar<"utf16">::char_type)) - } - )) - { - all_possible |= std::to_underlying(EncodingType::UTF16_LE); - } - } - // utf32 - { - if ((input_length % 4) == 0) - { - current_max = _mm512_max_epu32(_mm512_maskz_loadu_epi8((__mmask64{1} << remaining) - 1, it_input_current), current_max); - if (const auto outside_range = _mm512_cmp_epu32_mask(current_max, _mm512_set1_epi32(0x0010'ffff), _MM_CMPINT_GT); - outside_range == 0) - { - all_possible |= std::to_underlying(EncodingType::UTF32_LE); - } - } - } - - return static_cast(all_possible); - } - - if (Simd<"icelake.utf8">::validate(input)) - { - return EncodingType::UTF8; - } - - return EncodingType::UNKNOWN; - } - - [[nodiscard]] constexpr static auto encoding_of(const std::span input) noexcept -> EncodingType - { - static_assert(sizeof(char) == sizeof(char8_t)); - - const auto* char8_string = GAL_PROMETHEUS_SEMANTIC_UNRESTRICTED_CHAR_POINTER_CAST(char8_t, input.data()); - return encoding_of({char8_string, input.size()}); - } - }; -} diff --git a/src/chars/icelake_ascii.ixx b/src/chars/icelake_ascii.ixx deleted file mode 100644 index e64f732d..00000000 --- a/src/chars/icelake_ascii.ixx +++ /dev/null @@ -1,613 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#if __has_include() -#include -#endif -#if __has_include() -#include -#endif -#include - -export module gal.prometheus.chars:icelake.ascii; - -import std; -import gal.prometheus.meta; -import gal.prometheus.memory; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -import :encoding; -import :scalar.ascii; - -#else -#if __has_include() -#include -#endif -#if __has_include() -#include -#endif -#include - -#include -#include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -#endif - -// ReSharper disable once CppRedundantNamespaceDefinition -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::chars) -{ - template<> - class Simd<"icelake.ascii"> - { - public: - using scalar_type = Scalar<"ascii">; - - constexpr static auto input_category = scalar_type::input_category; - using input_type = scalar_type::input_type; - using char_type = scalar_type::char_type; - using pointer_type = scalar_type::pointer_type; - using size_type = scalar_type::size_type; - - template - [[nodiscard]] constexpr static auto validate(const input_type input) noexcept -> std::conditional_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - const auto ascii = _mm512_set1_epi8(static_cast(0x80)); - // used iff not ReturnResultType - auto running_or = _mm512_setzero_si512(); - - for (; it_input_current + 64 <= it_input_end; it_input_current += 64) - { - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - const auto in = _mm512_loadu_si512(it_input_current); - - if constexpr (ReturnResultType) - { - if (const auto not_ascii = _mm512_cmp_epu8_mask(in, ascii, _MM_CMPINT_NLT); - not_ascii) { return result_type{.error = ErrorCode::TOO_LARGE, .count = length_if_error + std::countr_zero(not_ascii)}; } - } - else - { - // running_or | (in & ascii) - running_or = _mm512_ternarylogic_epi32(running_or, in, ascii, 0xf8); - } - } - - if constexpr (ReturnResultType) - { - const auto in = _mm512_maskz_loadu_epi8((__mmask64{1} << (it_input_end - it_input_current)) - 1, it_input_current); - if (const auto not_ascii = _mm512_cmp_epu8_mask(in, ascii, _MM_CMPINT_NLT); - not_ascii) - { - return result_type{ - .error = ErrorCode::TOO_LARGE, - .count = static_cast(it_input_current - it_input_begin) + std::countr_zero(not_ascii) - }; - } - - return result_type{.error = ErrorCode::NONE, .count = input_length}; - } - else - { - if (it_input_current < it_input_end) - { - const auto in = _mm512_maskz_loadu_epi8((__mmask64{1} << (it_input_end - it_input_current)) - 1, it_input_current); - // running_or | (in & ascii) - running_or = _mm512_ternarylogic_epi32(running_or, in, ascii, 0xf8); - } - - return _mm512_test_epi8_mask(running_or, running_or) == 0; - } - } - - template - [[nodiscard]] constexpr static auto validate(const pointer_type input) noexcept -> std::conditional_t - { - return validate({input, std::char_traits::length(input)}); - } - - // note: we are not BOM aware - template - [[nodiscard]] constexpr static auto length(const input_type input) noexcept -> size_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - if constexpr (OutputCategory == CharsCategory::ASCII) { return input.size(); } // NOLINT(bugprone-branch-clone) - else if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) - { - auto eight_64_bits = _mm512_setzero_si512(); - - while (it_input_current + sizeof(__m512i) <= it_input_end) - { - const auto iterations = std::ranges::min( - static_cast((it_input_end - it_input_current) / sizeof(__m512i)), - static_cast(255) - ); - const auto this_turn_end = it_input_current + iterations * sizeof(__m512i) - sizeof(__m512i); - - auto runner = _mm512_setzero_si512(); - - for (; it_input_current + 4 * sizeof(__m512i) <= this_turn_end; it_input_current += 4 * sizeof(__m512i)) - { - // Load four __m512i vectors - const auto in_0 = _mm512_loadu_si512( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m512i *, it_input_current + 0 * sizeof(__m512i))); - const auto in_1 = _mm512_loadu_si512( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m512i *, it_input_current + 1 * sizeof(__m512i))); - const auto in_2 = _mm512_loadu_si512( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m512i *, it_input_current + 2 * sizeof(__m512i))); - const auto in_3 = _mm512_loadu_si512( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m512i *, it_input_current + 3 * sizeof(__m512i))); - - // Generate four masks - const auto mask_0 = _mm512_cmpgt_epi8_mask(_mm512_setzero_si512(), in_0); - const auto mask_1 = _mm512_cmpgt_epi8_mask(_mm512_setzero_si512(), in_1); - const auto mask_2 = _mm512_cmpgt_epi8_mask(_mm512_setzero_si512(), in_2); - const auto mask_3 = _mm512_cmpgt_epi8_mask(_mm512_setzero_si512(), in_3); - - // Apply the masks and subtract from the runner - const auto not_ascii_0 = _mm512_mask_set1_epi8(_mm512_setzero_si512(), mask_0, static_cast(0xff)); - const auto not_ascii_1 = _mm512_mask_set1_epi8(_mm512_setzero_si512(), mask_1, static_cast(0xff)); - const auto not_ascii_2 = _mm512_mask_set1_epi8(_mm512_setzero_si512(), mask_2, static_cast(0xff)); - const auto not_ascii_3 = _mm512_mask_set1_epi8(_mm512_setzero_si512(), mask_3, static_cast(0xff)); - - runner = _mm512_sub_epi8(runner, not_ascii_0); - runner = _mm512_sub_epi8(runner, not_ascii_1); - runner = _mm512_sub_epi8(runner, not_ascii_2); - runner = _mm512_sub_epi8(runner, not_ascii_3); - } - - for (; it_input_current <= this_turn_end; it_input_current += sizeof(__m512i)) - { - const auto in = _mm512_loadu_si512( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m512i*, it_input_current + 0 * sizeof(__m512i))); - const auto mask = _mm512_cmpgt_epi8_mask(_mm512_setzero_si512(), in); - const auto not_ascii = _mm512_mask_set1_epi8(_mm512_setzero_si512(), mask, static_cast(0xff)); - runner = _mm512_sub_epi8(runner, not_ascii); - } - - eight_64_bits = _mm512_add_epi64(eight_64_bits, _mm512_sad_epu8(runner, _mm512_setzero_si512())); - } - - const auto half_0 = _mm512_extracti64x4_epi64(eight_64_bits, 0); - const auto half_1 = _mm512_extracti64x4_epi64(eight_64_bits, 1); - - const auto result_length = - // number of 512-bit chunks that fits into the length. - input_length / sizeof(__m512i) * sizeof(__m512i) + // - _mm256_extract_epi64(half_0, 0) + - _mm256_extract_epi64(half_0, 1) + - _mm256_extract_epi64(half_0, 2) + - _mm256_extract_epi64(half_0, 3) + - _mm256_extract_epi64(half_1, 0) + - _mm256_extract_epi64(half_1, 1) + - _mm256_extract_epi64(half_1, 2) + - _mm256_extract_epi64(half_1, 3); - - return result_length + scalar_type::length( - {it_input_current, static_cast(it_input_end - it_input_current)} - ); - } - else if constexpr ( - OutputCategory == CharsCategory::UTF16_LE or - OutputCategory == CharsCategory::UTF16_BE or - OutputCategory == CharsCategory::UTF16 or - OutputCategory == CharsCategory::UTF32 - ) - { - return input.size(); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - // note: we are not BOM aware - template - [[nodiscard]] constexpr static auto length(const pointer_type input) noexcept -> size_type - { - return length({input, std::char_traits::length(input)}); - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert( - const input_type input, - typename output_type::pointer output - ) noexcept -> std::conditional_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); - if constexpr (ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(validate(input)); - } - - using output_pointer_type = typename output_type::pointer; - // using output_char_type = typename output_type::value_type; - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - const output_pointer_type it_output_begin = output; - output_pointer_type it_output_current = it_output_begin; - - if constexpr (OutputCategory == CharsCategory::ASCII) - { - std::memcpy(it_output_current, it_input_current, input_length * sizeof(char_type)); - it_input_current += input_length; - it_output_current += input_length; - } - else if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) - { - const auto process = [](const auto in, const auto in_length, output_pointer_type out) noexcept -> size_type - { - constexpr auto alternate_bits = 0x5555'5555'5555'5555ull; - - const auto non_ascii = _mm512_movepi8_mask(in); - const auto ascii = ~non_ascii; - // Mask to denote whether the byte is a leading byte that is not ascii. - // binary representation of -64: 1100'0000 - const auto sixth = _mm512_cmpge_epu8_mask(in, _mm512_set1_epi8(static_cast(-64))); - - // the bits in ascii are inverted and zeros are interspersed in between them. - const auto mask_a = ~_pdep_u64(ascii, alternate_bits); - const auto mask_b = ~_pdep_u64(ascii >> 32, alternate_bits); - // interleave bytes from top and bottom halves (abcd...ABCD -> aAbBcCdD). - const auto input_interleaved = _mm512_permutexvar_epi8( - // clang-format off - _mm512_set_epi32( - 0x3f1f'3e1e, - 0x3d1d'3c1c, - 0x3b1b'3a1a, - 0x3919'3818, - 0x3717'3616, - 0x3515'3414, - 0x3313'3212, - 0x3111'3010, - 0x2f0f'2e0e, - 0x2d0d'2c0c, - 0x2b0b'2a0a, - 0x2909'2808, - 0x2707'2606, - 0x2505'2404, - 0x2303'2202, - 0x2101'2000 - ), - // clang-format on - in); - - const auto output_a = [](const auto interleaved, const auto s, const auto mask) noexcept -> auto - { - // Upscale the bytes to 16-bit value, adding the 0b1100'0010 leading byte in the process. - // We adjust for the bytes that have their two most significant bits. This takes care of the first 32 bytes, assuming we interleaved the bytes. - // binary representation of -62: 1100'0010 - auto v = _mm512_shldi_epi16(interleaved, _mm512_set1_epi8(static_cast(-62)), 8); - v = _mm512_mask_add_epi16( - v, - static_cast<__mmask32>(s), - v, - // 1- 0x4000 = 1100 0000 0000 0001 - _mm512_set1_epi16(1 - 0x4000) - ); - // prune redundant bytes - return _mm512_maskz_compress_epi8(mask, v); - }(input_interleaved, sixth, mask_a); - - const auto output_b = [](const auto interleaved, const auto s, const auto mask) noexcept -> auto - { - // in the second 32-bit half, set first or second option based on whether original input is leading byte (second case) or not (first case). - const auto leading = _mm512_mask_blend_epi16( - static_cast<__mmask32>(s >> 32), - // 0000 0000 1101 0010 - _mm512_set1_epi16(0x00c2), - // 0100 0000 1100 0011 - _mm512_set1_epi16(0x40c3)); - const auto v = _mm512_ternarylogic_epi32( - interleaved, - leading, - _mm512_set1_epi16(static_cast(0xff00)), - // (interleaved & 0xff00) ^ leading - (240 & 170) ^ 204 - ); - // prune redundant bytes - return _mm512_maskz_compress_epi8(mask, v); - }(input_interleaved, sixth, mask_b); - - const auto out_size = static_cast(in_length + std::popcount(non_ascii)); - const auto out_size_a = static_cast(std::popcount(static_cast(non_ascii))) + 32; - - if constexpr (MaskOut) - { - // is the second half of the input vector used? - if (in_length > 32) - { - _mm512_mask_storeu_epi8(out, _bzhi_u64(~0ull, out_size_a), output_a); - _mm512_mask_storeu_epi8(out + out_size_a, _bzhi_u64(~0ull, out_size - out_size_a), output_b); - } - else { _mm512_mask_storeu_epi8(out, _bzhi_u64(~0ull, out_size), output_a); } - } - else - { - _mm512_storeu_si512(out, output_a); - _mm512_storeu_si512(out + out_size_a, output_b); - } - - return out_size; - }; - - const auto process_or_just_store = [process](const auto in, output_pointer_type out) noexcept -> size_type - { - const auto non_ascii = _mm512_movepi8_mask(in); - const auto count = std::popcount(non_ascii); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(count >= 0); - if (count != 0) { return process.template operator()(in, 64, out); } - - _mm512_storeu_si512(out, in); - return 64; - }; - - // if there's at least 128 bytes remaining, we don't need to mask the output. - for (; it_input_current + 128 <= it_input_end; it_input_current += 64) - { - const auto in = _mm512_loadu_si512(it_input_current); - it_output_current += process_or_just_store(in, it_output_current); - } - - // in the last 128 bytes, the first 64 may require masking the output - if (it_input_current + 64 <= it_input_end) - { - const auto in = _mm512_loadu_si512(it_input_current); - it_output_current += process.template operator()(in, 64, it_output_current); - it_input_current += 64; - } - - // with the last 64 bytes, the input also needs to be masked - if (it_input_current < it_input_end) - { - const auto in = _mm512_maskz_loadu_epi8( - _bzhi_u64( - ~0ull, - static_cast(it_input_end - it_input_current) - ), - it_input_current - ); - it_output_current += process.template operator()(in, it_input_end - it_input_current, it_output_current); - } - } - else if constexpr (OutputCategory == CharsCategory::UTF16_LE or OutputCategory == CharsCategory::UTF16_BE) - { - constexpr auto is_big_endian = OutputCategory != CharsCategory::UTF16_LE; - - const auto byte_flip = _mm512_setr_epi64( - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809, - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809, - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809, - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809 - ); - - // Round down to nearest multiple of 32 - const auto rounded_input_length = input_length & ~0x1f; - const auto it_rounded_input_end = it_input_begin + rounded_input_length; - - for (; it_input_current < it_rounded_input_end; it_input_current += 32, it_output_current += 32) - { - // Load 32 ascii characters into a 256-bit register - const auto in = _mm256_loadu_si256(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m256i *, it_input_current)); - // Zero extend each set of 8 ascii characters to 32 16-bit integers - const auto out = [byte_flip](const auto i) noexcept -> auto - { - if constexpr (is_big_endian) { return _mm512_shuffle_epi8(_mm512_cvtepu8_epi16(i), byte_flip); } - else - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'byte_flip' is not used [-Werror,-Wunused-lambda-capture] - (void)byte_flip; - #endif - - return _mm512_cvtepu8_epi16(i); - } - }(in); - // Store the results back to memory - _mm512_storeu_si512(it_output_current, out); - } - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_rounded_input_end); - - if (const auto remaining = input_length - rounded_input_length; - remaining != 0) - { - const auto mask = (__mmask32{1} << (input_length - rounded_input_length)) - 1; - const auto in = _mm256_maskz_loadu_epi8(mask, it_input_current); - // Zero extend each set of 8 ascii characters to 32 16-bit integers - const auto out = [byte_flip](const auto i) noexcept -> auto - { - if constexpr (is_big_endian) { return _mm512_shuffle_epi8(_mm512_cvtepu8_epi16(i), byte_flip); } - else - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'byte_flip' is not used [-Werror,-Wunused-lambda-capture] - (void)byte_flip; - #endif - - return _mm512_cvtepu8_epi16(i); - } - }(in); - // Store the results back to memory - _mm512_mask_storeu_epi16(it_output_current, mask, out); - it_input_current += remaining; - it_output_current += remaining; - } - } - else if constexpr (OutputCategory == CharsCategory::UTF32) - { - // Round down to nearest multiple of 16 - const auto rounded_input_length = input_length & ~0x1f; - const auto it_rounded_input_end = it_input_begin + rounded_input_length; - - for (; it_input_current < it_rounded_input_end; it_input_current += 16, it_output_current += 16) - { - // Load 16 ascii characters into a 128-bit register - const auto in = _mm_loadu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m128i *, it_input_current)); - // Zero extend each set of 8 ascii characters to 16 32-bit integers - const auto out = _mm512_cvtepu8_epi32(in); - // Store the results back to memory - _mm512_storeu_si512(it_output_current, out); - } - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_rounded_input_end); - - if (const auto remaining = input_length - rounded_input_length; - remaining != 0) - { - // fixme: We can't assume that conversions are always successful - const auto processed_length = - scalar_type::convert< - CharsCategory::UTF32, - InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT, - CheckNextBlock - >( - {it_input_current, input_length - rounded_input_length}, - it_output_current - ); - it_input_current += processed_length; - it_output_current += processed_length; - } - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE("Unknown or unsupported `OutputCategory` (we don't know the `endian` by UTF16, so it's not allowed to use it here)."); - } - - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { return static_cast(it_output_current - it_output_begin); } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::NONE, .count = static_cast(it_input_current - it_input_begin)}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert( - const pointer_type input, - typename output_type::pointer output - ) noexcept -> std::conditional_t // - { - return convert({input, std::char_traits::length(input)}, output); - } - - template< - typename StringType, - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - requires requires(StringType& string) - { - string.resize(std::declval()); - { - string.data() - } -> std::convertible_to::pointer>; - } - [[nodiscard]] constexpr static auto convert(const input_type input) noexcept -> StringType - { - StringType result{}; - result.resize(length(input)); - - (void)convert(input, result.data()); - return result; - } - - template< - typename StringType, - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - requires requires(StringType& string) - { - string.resize(std::declval()); - { - string.data() - } -> std::convertible_to::pointer>; - } - [[nodiscard]] constexpr static auto convert(const pointer_type input) noexcept -> StringType - { - StringType result{}; - result.resize(length(input)); - - return convert({input, std::char_traits::length(input)}, result.data()); - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert(const input_type input) noexcept -> std::basic_string::value_type> - { - std::basic_string::value_type> result{}; - result.resize(length(input)); - - (void)convert(input, result.data()); - return result; - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert(const pointer_type input) noexcept -> std::basic_string::value_type> - { - std::basic_string::value_type> result{}; - result.resize(length(input)); - - return convert({input, std::char_traits::length(input)}, result.data()); - } - }; - - template<> - struct simd_processor_of - { - using type = Simd<"icelake.ascii">; - }; -} diff --git a/src/chars/icelake_utf16.ixx b/src/chars/icelake_utf16.ixx deleted file mode 100644 index 6e4cb059..00000000 --- a/src/chars/icelake_utf16.ixx +++ /dev/null @@ -1,1072 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#if __has_include() -#include -#endif -#if __has_include() -#include -#endif -#include - -export module gal.prometheus.chars:icelake.utf16; - -import std; -import gal.prometheus.meta; -import gal.prometheus.memory; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -import :encoding; -import :scalar.utf16; - -#else -#if __has_include() -#include -#endif -#if __has_include() -#include -#endif -#include - -#include -#include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -#endif - -// ReSharper disable once CppRedundantNamespaceDefinition -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::chars) -{ - template<> - class Simd<"icelake.utf16"> - { - public: - using scalar_type = Scalar<"utf16">; - - constexpr static auto input_category = scalar_type::input_category; - using input_type = scalar_type::input_type; - using char_type = scalar_type::char_type; - using pointer_type = scalar_type::pointer_type; - using size_type = scalar_type::size_type; - - template - [[nodiscard]] constexpr static auto validate(const input_type input) noexcept -> std::conditional_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - const auto byte_flip = _mm512_setr_epi64( - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809, - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809, - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809, - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809); - - for (; it_input_current + 32 <= it_input_end; it_input_current += 32) - { - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - const auto in = [byte_flip](const auto c) noexcept - { - if constexpr (Endian == std::endian::native) - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'byte_flip' is not used [-Werror,-Wunused-lambda-capture] - (void)byte_flip; - #endif - - return _mm512_loadu_si512(c); - } - else { return _mm512_shuffle_epi8(_mm512_loadu_si512(c), byte_flip); } - }(it_input_current); - const auto diff = _mm512_sub_epi16(in, _mm512_set1_epi16(static_cast(0xd800))); - - if (const auto surrogates = _mm512_cmplt_epu16_mask(diff, _mm512_set1_epi16(0x0800)); - surrogates) - { - const auto high_surrogates = _mm512_cmplt_epu16_mask(diff, _mm512_set1_epi16(0x0400)); - const auto low_surrogates = surrogates ^ high_surrogates; - // high must be followed by low - if ((high_surrogates << 1) != low_surrogates) - { - if constexpr (ReturnResultType) - { - const auto extra_high = std::countr_zero(static_cast(high_surrogates & ~(low_surrogates >> 1))); - const auto extra_low = std::countr_zero(static_cast(low_surrogates & ~(high_surrogates << 1))); - - return result_type{.error = ErrorCode::SURROGATE, .count = length_if_error + std::ranges::min(extra_high, extra_low)}; - } - else { return false; } - } - - if (const auto ends_with_high = ((high_surrogates & 0x8000'0000) != 0); - ends_with_high) - { - // advance only by 31 code units so that we start with the high surrogate on the next round. - // see `for (; it_input_current + 32 <= it_input_end; it_input_current += 32)` - it_input_current -= 1; - } - } - } - - if (it_input_current < it_input_end) - { - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - const auto in = [byte_flip](const auto c, const auto e) noexcept - { - if constexpr (Endian == std::endian::native) - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'byte_flip' is not used [-Werror,-Wunused-lambda-capture] - (void)byte_flip; - #endif - - return _mm512_maskz_loadu_epi16((__mmask32{1} << (e - c)) - 1, c); - } - else { return _mm512_shuffle_epi8(_mm512_maskz_loadu_epi16((__mmask32{1} << (e - c)) - 1, c), byte_flip); } - }(it_input_current, it_input_end); - const auto diff = _mm512_sub_epi16(in, _mm512_set1_epi16(static_cast(0xd800))); - - if (const auto surrogates = _mm512_cmplt_epu16_mask(diff, _mm512_set1_epi16(0x0800)); - surrogates) - { - const auto high_surrogates = _mm512_cmplt_epu16_mask(diff, _mm512_set1_epi16(0x0400)); - const auto low_surrogates = surrogates ^ high_surrogates; - // high must be followed by low - if ((high_surrogates << 1) != low_surrogates) - { - if constexpr (ReturnResultType) - { - const auto extra_high = std::countr_zero(static_cast(high_surrogates & ~(low_surrogates >> 1))); - const auto extra_low = std::countr_zero(static_cast(low_surrogates & ~(high_surrogates << 1))); - - return result_type{.error = ErrorCode::SURROGATE, .count = length_if_error + std::ranges::min(extra_high, extra_low)}; - } - else { return false; } - } - } - } - - if constexpr (ReturnResultType) { return result_type{.error = ErrorCode::NONE, .count = input_length}; } - else { return true; } - } - - template - [[nodiscard]] constexpr static auto validate(const pointer_type input) noexcept -> std::conditional_t - { - return validate({input, std::char_traits::length(input)}); - } - - // note: we are not BOM aware - template - [[nodiscard]] constexpr static auto length(const input_type input) noexcept -> size_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - const auto byte_flip = _mm512_setr_epi64( - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809, - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809, - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809, - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809 - ); - - if constexpr (OutputCategory == CharsCategory::ASCII) { return input.size(); } // NOLINT(bugprone-branch-clone) - else if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) - { - const auto v_007f = _mm512_set1_epi16(0x007f); - const auto v_07ff = _mm512_set1_epi16(0x07ff); - const auto v_dfff = _mm512_set1_epi16(static_cast(0xdfff)); - const auto v_d800 = _mm512_set1_epi16(static_cast(0xd800)); - - auto result_length = static_cast(0); - - for (; it_input_current + 32 <= it_input_end; it_input_current += 32) - { - const auto in = [byte_flip](const auto c) noexcept - { - if constexpr (Endian == std::endian::native) - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'byte_flip' is not used [-Werror,-Wunused-lambda-capture] - (void)byte_flip; - #endif - - return _mm512_loadu_si512(c); - } - else - { - return _mm512_shuffle_epi8(_mm512_loadu_si512(c), byte_flip); - } - }(it_input_current); - - const auto ascii_bitmask = _mm512_cmple_epu16_mask(in, v_007f); - const auto two_bytes_bitmask = _mm512_mask_cmple_epu16_mask(~ascii_bitmask, in, v_07ff); - const auto surrogates_bitmask = - _mm512_mask_cmple_epu16_mask(~(ascii_bitmask | two_bytes_bitmask), in, v_dfff) & - _mm512_mask_cmpge_epu16_mask(~(ascii_bitmask | two_bytes_bitmask), in, v_d800); - - const auto ascii_count = std::popcount(ascii_bitmask); - const auto two_bytes_count = std::popcount(two_bytes_bitmask); - const auto surrogates_bytes_count = std::popcount(surrogates_bitmask); - const auto three_bytes_count = 32 - ascii_count - two_bytes_count - surrogates_bytes_count; - - result_length += ascii_count + 2 * two_bytes_count + 2 * surrogates_bytes_count + 3 * three_bytes_count; - } - - return result_length + scalar_type::length({it_input_current, input_length - (it_input_current - it_input_begin)}); - } - else if constexpr (OutputCategory == CharsCategory::UTF16_LE or OutputCategory == CharsCategory::UTF16_BE or OutputCategory == CharsCategory::UTF16) - { - return input.size(); - } - else if constexpr (OutputCategory == CharsCategory::UTF32) - { - const auto low = _mm512_set1_epi16(static_cast(0xdc00)); - const auto high = _mm512_set1_epi16(static_cast(0xdfff)); - - auto result_length = static_cast(0); - - for (; it_input_current + 32 <= it_input_end; it_input_current += 32) - { - const auto in = [byte_flip](const auto c) noexcept - { - if constexpr (Endian == std::endian::native) - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'byte_flip' is not used [-Werror,-Wunused-lambda-capture] - (void)byte_flip; - #endif - - return _mm512_loadu_si512(c); - } - else { return _mm512_shuffle_epi8(_mm512_loadu_si512(c), byte_flip); } - }(it_input_current); - - const auto not_high_surrogate_bitmask = static_cast(_mm512_cmpgt_epu16_mask(in, high) | _mm512_cmplt_epu16_mask(in, low)); - - const auto not_high_surrogate_count = std::popcount(not_high_surrogate_bitmask); - - result_length += not_high_surrogate_count; - } - - return result_length + scalar_type::length({it_input_current, input_length - (it_input_current - it_input_begin)}); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - // note: we are not BOM aware - template - [[nodiscard]] constexpr static auto length(const pointer_type input) noexcept -> size_type - { - return length({input, std::char_traits::length(input)}); - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - std::endian Endian = std::endian::native, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert( - const input_type input, - typename output_type::pointer output - ) noexcept -> std::conditional_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); - if constexpr (ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(validate(input)); - } - - using output_pointer_type = typename output_type::pointer; - using output_char_type = typename output_type::value_type; - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - const output_pointer_type it_output_begin = output; - output_pointer_type it_output_current = it_output_begin; - - const auto byte_flip = _mm512_setr_epi64( - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809, - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809, - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809, - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809); - - if constexpr (OutputCategory == CharsCategory::ASCII) - { - const auto v_00ff = _mm512_set1_epi32(0x00ff); - const auto shuffle_mask = _mm512_set_epi8( - // clang-format off - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 62, - 60, - 58, - 56, - 54, - 52, - 50, - 48, - 46, - 44, - 42, - 40, - 38, - 36, - 34, - 32, - 30, - 28, - 26, - 24, - 22, - 20, - 18, - 16, - 14, - 12, - 10, - 8, - 6, - 4, - 2, - 0 - // clang-format on - ); - - for (; it_input_current + 32 <= it_input_end; it_input_current += 32, it_output_current += 32) - { - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - const auto in = [byte_flip](const auto c) noexcept - { - if constexpr ((Endian == std::endian::little) == (OutputCategory == CharsCategory::UTF16_LE)) - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'byte_flip' is not used [-Werror,-Wunused-lambda-capture] - (void)byte_flip; - #endif - - return _mm512_loadu_si512(c); - } - else { return _mm512_shuffle_epi8(_mm512_loadu_si512(c), byte_flip); } - }(it_input_current); - - if (_mm512_cmpgt_epu16_mask(in, v_00ff)) - { - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - const auto it = std::ranges::find_if( - it_input_current, - it_input_current + 32, - [](const auto data) noexcept { return data > 0xff; }); - // fixme: write or ignore? - { - std::ranges::transform( - std::ranges::subrange{it_input_current, it}, - it_output_current, - [](const auto word) noexcept -> output_char_type - { - return static_cast(word); - } - ); - it_output_current += std::ranges::distance(it_input_current, it); - } - - return result_type{.error = ErrorCode::TOO_LARGE, .count = length_if_error + std::ranges::distance(it_input_current, it)}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - _mm256_storeu_si256( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m256i*, it_output_current), - _mm512_castsi512_si256(_mm512_permutexvar_epi8(shuffle_mask, in)) - ); - } - - if (const auto remaining = it_input_end - it_input_current; - remaining != 0) - { - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - const auto mask = static_cast<__mmask16>((1 << remaining) - 1); - const auto in = [byte_flip, mask](const auto c) noexcept - { - if constexpr ((Endian == std::endian::little) == (OutputCategory == CharsCategory::UTF16_LE)) - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'byte_flip' is not used [-Werror,-Wunused-lambda-capture] - (void)byte_flip; - #endif - - return _mm512_maskz_loadu_epi16(mask, c); - } - else { return _mm512_shuffle_epi8(_mm512_maskz_loadu_epi16(mask, c), byte_flip); } - }(it_input_current); - - if (_mm512_cmpgt_epu16_mask(in, v_00ff)) - { - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - const auto it = std::ranges::find_if( - it_input_current, - it_input_current + remaining, - [](const auto data) noexcept { return data > 0xff; }); - // fixme: write or ignore? - { - std::ranges::transform( - std::ranges::subrange{it_input_current, it}, - it_output_current, - [](const auto word) noexcept -> output_char_type - { - return static_cast(word); - } - ); - it_output_current += std::ranges::distance(it_input_current, it); - } - - return result_type{.error = ErrorCode::TOO_LARGE, .count = length_if_error + std::ranges::distance(it_input_current, it)}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - _mm256_mask_storeu_epi8(it_output_current, mask, _mm512_castsi512_si256(_mm512_permutexvar_epi8(shuffle_mask, in))); - it_input_current += remaining; - it_output_current += remaining; - } - } - else if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) - { - int advanced_iteration = 0; - int carry = 0; - - const auto process = functional::y_combinator{ - [&](auto self, const __m512i current_in, const __mmask32 current_in_mask) noexcept -> void - { - const auto v_0000_0080 = _mm512_set1_epi16(0x0000'0080); - const auto v_0000_3f3f = _mm512_set1_epi16(0x0000'3f3f); - const auto v_0000_ffff = _mm512_set1_epi16(static_cast(0x0000'ffff)); - const auto v_0000_0800 = _mm512_set1_epi16(0x0000'0800); - const auto v_0000_80c0 = _mm512_set1_epi16(static_cast(0x0000'80c0)); - const auto v_8080_e000 = _mm512_set1_epi32(static_cast(0x8080'e000)); - const auto v_0000_fc00 = _mm512_set1_epi16(static_cast(0x0000'fc00)); - const auto v_0000_d800 = _mm512_set1_epi16(static_cast(0x0000'd800)); - const auto v_0000_dc00 = _mm512_set1_epi16(static_cast(0x0000'dc00)); - const auto v_8080_80f0 = _mm512_set1_epi32(static_cast(0x8080'80f0)); - const auto v_fca0_2400 = _mm512_set1_epi32(static_cast(0xfca0'2400)); - const auto v_80c0_0000 = _mm512_set1_epi32(static_cast(0x80c0'0000)); - const auto v_ffff_ffff = _mm512_set1_epi32(static_cast(0xffff'ffff)); - const auto v_0001_0101 = _mm512_set1_epi32(0x0001'0101); - const auto v_3f3f_3f3f = _mm512_set1_epi32(0x3f3f'3f3f); - - const auto v_2026_2c32_0006_0c12 = _mm512_set1_epi64(0x2026'2c32'0006'0c12); - - const auto is_234_byte = _mm512_mask_cmp_epu16_mask(current_in_mask, current_in, v_0000_0080, _MM_CMPINT_NLT); - // ASCII only path - if (_ktestz_mask32_u8(current_in_mask, is_234_byte)) - { - _mm512_mask_cvtepi16_storeu_epi8(it_output_current, current_in_mask, current_in); - it_output_current += 31; - - carry = 0; - return; - } - - const auto is_12_byte = _mm512_cmp_epu16_mask(current_in, v_0000_0800, _MM_CMPINT_LT); - // 1/2 byte path - if (_ktestc_mask32_u8(is_12_byte, current_in_mask)) - { - // (A|B)&C - const auto two_bytes = _mm512_ternarylogic_epi32( - _mm512_slli_epi16(current_in, 8), - _mm512_srli_epi16(current_in, 6), - v_0000_3f3f, - 0xa8 - ); - const auto compare_mask = _mm512_mask_blend_epi16(current_in_mask, v_0000_ffff, v_0000_0800); - const auto in = _mm512_mask_add_epi16(current_in, is_234_byte, two_bytes, v_0000_80c0); - const auto smoosh = _mm512_cmp_epu8_mask(in, compare_mask, _MM_CMPINT_NLT); - const auto out = _mm512_maskz_compress_epi8(smoosh, in); - - _mm512_mask_storeu_epi8( - it_output_current, - _cvtu64_mask64(_pext_u64(_cvtmask64_u64(smoosh), _cvtmask64_u64(smoosh))), - out - ); - it_output_current += 31 + std::popcount(_cvtmask32_u32(is_234_byte)); - - carry = 0; - return; - } - - auto low = _mm512_cvtepu16_epi32(_mm512_castsi512_si256(current_in)); - auto high = _mm512_cvtepu16_epi32(_mm512_extracti32x8_epi32(current_in, 1)); - auto tag_low = v_8080_e000; - auto tag_high = v_8080_e000; - - const auto masked_in = _mm512_and_epi32(current_in, v_0000_fc00); - const auto high_surrogate = _mm512_mask_cmp_epu16_mask(current_in_mask, masked_in, v_0000_d800, _MM_CMPINT_EQ); - const auto low_surrogate = _mm512_cmp_epu16_mask(masked_in, v_0000_dc00, _MM_CMPINT_EQ); - - int carry_out = 0; - // handle surrogates - if (not _kortestz_mask32_u8(high_surrogate, low_surrogate)) - { - const auto high_surrogate_high = _kshiftri_mask32(high_surrogate, 16); - - tag_low = _mm512_mask_mov_epi32(tag_low, static_cast<__mmask16>(high_surrogate), v_8080_80f0); - tag_high = _mm512_mask_mov_epi32(tag_high, static_cast<__mmask16>(high_surrogate_high), v_8080_80f0); - - low = [low, high_surrogate](const auto l) mutable noexcept - { - low = _mm512_mask_slli_epi32(low, static_cast<__mmask16>(high_surrogate), low, 10); - low = _mm512_mask_add_epi32(low, static_cast<__mmask16>(high_surrogate), low, l); - return low; - }(_mm512_add_epi32(_mm512_alignr_epi32(high, low, 1), v_fca0_2400)); - high = [high, high_surrogate_high](const auto h) mutable noexcept - { - high = _mm512_mask_slli_epi32(high, static_cast<__mmask16>(high_surrogate_high), high, 10); - high = _mm512_mask_add_epi32(high, static_cast<__mmask16>(high_surrogate_high), high, h); - - return high; - }(_mm512_add_epi32(_mm512_alignr_epi32(low, high, 1), v_fca0_2400)); - - carry_out = static_cast(_cvtu32_mask32(_kshiftri_mask32(high_surrogate, 30))); - - const auto low_mask = _cvtmask32_u32(low_surrogate); - const auto high_mask = _cvtmask32_u32(high_surrogate); - // check for mismatched surrogates - if ((high_mask + high_mask + carry) ^ low_mask) - { - const auto low_no_high = low_mask & ~(high_mask + high_mask + carry); - const auto high_no_low = high_mask & ~(low_mask >> 1); - const auto length = std::countr_zero(low_no_high | high_no_low); - - const auto in_mask = __mmask32{0x7fff'ffff} & ((1 << length) - 1); - const auto in = _mm512_maskz_mov_epi16(in_mask, current_in); - - advanced_iteration = length - 31; - - return self(in, in_mask); - } - } - - high = _mm512_maskz_mov_epi32(_cvtu32_mask16(0x0000'7fff), high); - carry = carry_out; - - const auto out_mask = _kandn_mask32(low_surrogate, current_in_mask); - const auto out_mask_high = _kshiftri_mask32(out_mask, 16); - const auto magic_low = _mm512_mask_blend_epi32(static_cast<__mmask16>(out_mask), v_ffff_ffff, v_0001_0101); - const auto magic_high = _mm512_mask_blend_epi32(static_cast<__mmask16>(out_mask_high), v_ffff_ffff, v_0001_0101); - - const auto is_1_byte = _knot_mask32(is_234_byte); - const auto is_1_byte_high = _kshiftri_mask32(is_1_byte, 16); - const auto is_12_byte_high = _kshiftri_mask32(is_12_byte, 16); - - tag_low = _mm512_mask_mov_epi32(tag_low, static_cast<__mmask16>(is_12_byte), v_80c0_0000); - tag_high = _mm512_mask_mov_epi32(tag_high, static_cast<__mmask16>(is_12_byte_high), v_80c0_0000); - - const auto multi_shift_low = _mm512_mask_slli_epi32( - _mm512_ternarylogic_epi32( - _mm512_multishift_epi64_epi8(v_2026_2c32_0006_0c12, low), - v_3f3f_3f3f, - tag_low, - 0xea), - static_cast<__mmask16>(is_1_byte), - low, - 24); - const auto multi_shift_high = _mm512_mask_slli_epi32( - _mm512_ternarylogic_epi32( - _mm512_multishift_epi64_epi8(v_2026_2c32_0006_0c12, high), - v_3f3f_3f3f, - tag_high, - 0xea), - static_cast<__mmask16>(is_1_byte_high), - high, - 24); - - const auto want_low = _mm512_cmp_epu8_mask(multi_shift_low, magic_low, _MM_CMPINT_NLT); - const auto want_high = _mm512_cmp_epu8_mask(multi_shift_high, magic_high, _MM_CMPINT_NLT); - const auto want_low_mask = _cvtmask64_u64(want_low); - const auto want_high_mask = _cvtmask64_u64(want_high); - const auto length_low = std::popcount(want_low_mask); - const auto length_high = std::popcount(want_high_mask); - - const auto out_low = _mm512_maskz_compress_epi8(want_low, multi_shift_low); - const auto out_high = _mm512_maskz_compress_epi8(want_high, multi_shift_high); - - _mm512_mask_storeu_epi8(it_output_current, _cvtu64_mask64(_pext_u64(want_low_mask, want_low_mask)), out_low); - it_output_current += length_low; - _mm512_mask_storeu_epi8(it_output_current, _cvtu64_mask64(_pext_u64(want_high_mask, want_high_mask)), out_high); - it_output_current += length_high; - } - }; - - for (; it_input_current + 32 <= it_input_end; it_input_current += 31) - { - const auto in = [byte_flip](const auto c) noexcept - { - if constexpr (Endian == std::endian::native) - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'byte_flip' is not used [-Werror,-Wunused-lambda-capture] - (void)byte_flip; - #endif - - return _mm512_loadu_si512(c); - } - else - { - return _mm512_shuffle_epi8(_mm512_loadu_si512(c), byte_flip); - } - }(it_input_current); - const auto in_mask = _cvtu32_mask32(0x7fff'ffff); - - process(in, in_mask); - } - it_output_current -= advanced_iteration; - - if (const auto remaining = it_input_end - it_input_current; - remaining != 0) - { - const auto in_mask = _cvtu32_mask32((1 << remaining) - 1); - const auto in = [byte_flip, in_mask](const auto c) noexcept - { - if constexpr (Endian == std::endian::native) - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'byte_flip' is not used [-Werror,-Wunused-lambda-capture] - (void)byte_flip; - #endif - - return _mm512_maskz_loadu_epi16(in_mask, c); - } - else { return _mm512_shuffle_epi8(_mm512_maskz_loadu_epi16(in_mask, c), byte_flip); } - }(it_input_current); - - advanced_iteration = static_cast(remaining - 31); - it_input_current += remaining; - - process(in, in_mask); - it_output_current -= advanced_iteration; - } - } - else if constexpr (OutputCategory == CharsCategory::UTF16_LE or OutputCategory == CharsCategory::UTF16_BE) - { - if constexpr ((Endian == std::endian::little) == (OutputCategory == CharsCategory::UTF16_LE)) - { - std::memcpy(it_output_current, it_input_current, input_length * sizeof(char_type)); - } - else - { - flip_endian(input, it_output_current); - } - - it_input_current += input_length; - it_output_current += input_length; - } - else if constexpr (OutputCategory == CharsCategory::UTF32) - { - const auto process = [&]() noexcept -> bool - { - const auto v_0000_fc00 = _mm512_set1_epi16(static_cast(0x0000'fc00)); - const auto v_0000_d800 = _mm512_set1_epi16(static_cast(0x0000'd800)); - const auto v_0000_dc00 = _mm512_set1_epi16(static_cast(0x0000'dc00)); - - __mmask32 carry = 0; - - while (it_input_current + 32 <= it_input_end) - { - const auto in = [byte_flip](const auto c) noexcept - { - if constexpr (Endian == std::endian::native) - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'byte_flip' is not used [-Werror,-Wunused-lambda-capture] - (void)byte_flip; - #endif - - return _mm512_loadu_si512(c); - } - else { return _mm512_shuffle_epi8(_mm512_loadu_si512(c), byte_flip); } - }(it_input_current); - - const auto high_bitmask = _mm512_cmpeq_epi16_mask(_mm512_and_si512(in, v_0000_fc00), v_0000_d800); - const auto low_bitmask = _mm512_cmpeq_epi16_mask(_mm512_and_si512(in, v_0000_fc00), v_0000_dc00); - - if (high_bitmask | low_bitmask) - { - // surrogate pair(s) in a register - // A high surrogate must be followed by low one and a low one must be preceded by a high one. - // If valid, value should be equal to 0 - const auto value = low_bitmask ^ (carry | (high_bitmask << 1)); - - if (value == 0) - { - // valid case - // Input surrogate pair: - // |1101.11aa.aaaa.aaaa|1101.10bb.bbbb.bbbb| - // low surrogate high surrogate - - // Expand all code units to 32-bit code units - // in > |0000.0000.0000.0000.1101.11aa.aaaa.aaaa|0000.0000.0000.0000.1101.10bb.bbbb.bbbb| - const auto first = _mm512_cvtepu16_epi32(_mm512_castsi512_si256(in)); - const auto second = _mm512_cvtepu16_epi32(_mm512_extracti32x8_epi32(in, 1)); - - // Shift by one 16-bit word to align low surrogates with high surrogates - // in > |0000.0000.0000.0000.1101.11aa.aaaa.aaaa|0000.0000.0000.0000.1101.10bb.bbbb.bbbb| - // shifted > |????.????.????.????.????.????.????.????|0000.0000.0000.0000.1101.11aa.aaaa.aaaa| - const auto shifted_first = _mm512_alignr_epi32(second, first, 1); - const auto shifted_second = _mm512_alignr_epi32(_mm512_setzero_si512(), second, 1); - - // Align all high surrogates in first and second by shifting to the left by 10 bits - // |0000.0000.0000.0000.1101.11aa.aaaa.aaaa|0000.0011.0110.bbbb.bbbb.bb00.0000.0000| - const auto aligned_first = _mm512_mask_slli_epi32(first, static_cast<__mmask16>(high_bitmask), first, 10); - const auto aligned_second = _mm512_mask_slli_epi32(second, high_bitmask >> 16, second, 10); - - // Remove surrogate prefixes and add offset 0x0001'0000 by adding in, shifted and constant - // constant > |1111.1100.1010.0000.0010.0100.0000.0000|1111.1100.1010.0000.0010.0100.0000.0000| - // in > |0000.0000.0000.0000.1101.11aa.aaaa.aaaa|0000.0011.0110.bbbb.bbbb.bb00.0000.0000| - // shifted > |????.????.????.????.????.????.????.????|0000.0000.0000.0000.1101.11aa.aaaa.aaaa| - const auto constant = _mm512_set1_epi32(static_cast(0b1111'1100'1010'0000'0010'0100'0000'0000)); - - const auto added_first = _mm512_mask_add_epi32(aligned_first, static_cast<__mmask16>(high_bitmask), aligned_first, shifted_first); - const auto added_second = _mm512_mask_add_epi32(aligned_second, high_bitmask >> 16, aligned_second, shifted_second); - - const auto utf32_first = _mm512_mask_add_epi32(added_first, static_cast<__mmask16>(high_bitmask), added_first, constant); - const auto utf32_second = _mm512_mask_add_epi32(added_second, high_bitmask >> 16, added_second, constant); - - // Store all valid UTF-32 code units (low surrogate positions and 32nd word are invalid) - const auto valid = ~low_bitmask & 0x7fff'ffff; - - const auto compressed_first = _mm512_maskz_compress_epi32(static_cast<__mmask16>(valid), utf32_first); - const auto compressed_second = _mm512_maskz_compress_epi32(valid >> 16, utf32_second); - - const auto length_first = std::popcount(static_cast(valid)); - const auto length_second = std::popcount(static_cast(valid >> 16)); - - _mm512_storeu_si512(it_output_current, compressed_first); - it_output_current += length_first; - // _mm512_storeu_si512(it_output_current, compressed_second); - _mm512_mask_storeu_epi32(it_output_current, static_cast<__mmask16>(1 << length_second) - 1, compressed_second); - it_output_current += length_second; - - // Only process 31 code units, but keep track if the 31st word is a high surrogate as a carry - it_input_current += 31; - carry = (high_bitmask >> 30) & 0x1; - } - else - { - // invalid case - it_input_current += carry; - return false; - } - } - else - { - // no surrogates - // extend all thirty-two 16-bit code units to thirty-two 32-bit code units - _mm512_storeu_si512(it_output_current, _mm512_cvtepu16_epi32(_mm512_castsi512_si256(in))); - it_output_current += 16; - _mm512_storeu_si512(it_output_current, _mm512_cvtepu16_epi32(_mm512_extracti32x8_epi32(in, 1))); - it_output_current += 16; - - it_input_current += 32; - carry = 0; - } - } - - it_input_current += carry; - return true; - }; - - const auto result = process(); - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - if (const auto remaining = static_cast(it_input_end - it_input_current); - not result or remaining != 0) - { - if (const auto scalar_result = scalar_type::convert( - {it_input_current, remaining}, - it_output_current - ); - not scalar_result) - { - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = scalar_result.error, .count = length_if_error + scalar_result.count}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - else - { - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { it_output_current += scalar_result; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) { it_input_current += scalar_result.count; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - } - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE("Unknown or unsupported `OutputCategory` (we don't know the `endian` by UTF16, so it's not allowed to use it here)."); - } - - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { return static_cast(it_output_current - it_output_begin); } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::NONE, .count = static_cast(it_input_current - it_input_begin)}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - std::endian Endian = std::endian::native, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert( - const pointer_type input, - typename output_type::pointer output - ) noexcept -> std::conditional_t - { - return convert({input, std::char_traits::length(input)}, output); - } - - template< - typename StringType, - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - std::endian Endian = std::endian::native, - bool CheckNextBlock = true - > - requires requires(StringType& string) - { - string.resize(std::declval()); - { - string.data() - } -> std::convertible_to::pointer>; - } - [[nodiscard]] constexpr static auto convert(const input_type input) noexcept -> StringType - { - StringType result{}; - result.resize(length(input)); - - (void)convert(input, result.data()); - return result; - } - - template< - typename StringType, - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - std::endian Endian = std::endian::native, - bool CheckNextBlock = true - > - requires requires(StringType& string) - { - string.resize(std::declval()); - { - string.data() - } -> std::convertible_to::pointer>; - } - [[nodiscard]] constexpr static auto convert(const pointer_type input) noexcept -> StringType - { - StringType result{}; - result.resize(length(input)); - - return convert({input, std::char_traits::length(input)}, result.data()); - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - std::endian Endian = std::endian::native, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert(const input_type input) noexcept -> std::basic_string::value_type> - { - std::basic_string::value_type> result{}; - result.resize(length(input)); - - (void)convert(input, result.data()); - return result; - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - std::endian Endian = std::endian::native, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert(const pointer_type input) noexcept -> std::basic_string::value_type> - { - std::basic_string::value_type> result{}; - result.resize(length(input)); - - return convert({input, std::char_traits::length(input)}, result.data()); - } - - /*constexpr*/ - static auto flip_endian(const input_type input, const output_type::pointer output) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - const auto it_output_begin = output; - auto it_output_current = it_output_begin; - - const auto byte_flip = _mm512_setr_epi64( - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809, - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809, - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809, - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809); - - for (; it_input_current + 32 <= it_input_end; it_input_current += 32, it_output_current += 32) - { - const auto utf16 = _mm512_shuffle_epi8(_mm512_loadu_si512(it_input_current), byte_flip); - _mm512_storeu_si512(it_output_current, utf16); - } - - if (const auto remaining = static_cast(it_input_end - it_input_current); - remaining != 0) - { - const auto mask = static_cast<__mmask32>((1 << remaining) - 1); - const auto utf16 = _mm512_shuffle_epi8(_mm512_maskz_loadu_epi16(mask, it_input_current), byte_flip); - _mm512_mask_storeu_epi16(it_output_current, mask, utf16); - } - } - - template - requires requires(StringType& string) - { - string.resize(std::declval()); - { - string.data() - } -> std::convertible_to::pointer>; - } - [[nodiscard]] /*constexpr*/ static auto flip_endian(const input_type input) noexcept -> StringType - { - StringType result{}; - result.resize(length(input)); - - flip_endian(input, result.data()); - return result; - } - - [[nodiscard]] /*constexpr*/ static auto flip_endian(const input_type input) noexcept -> std::basic_string::value_type> - { - std::basic_string::value_type> result{}; - result.resize(length(input)); - - flip_endian(input, result.data()); - return result; - } - }; - - template<> - struct simd_processor_of - { - using type = Simd<"icelake.utf16">; - }; - - template<> - struct simd_processor_of : simd_processor_of {}; - - template<> - struct simd_processor_of : simd_processor_of {}; -} // namespace gal::prometheus::chars diff --git a/src/chars/icelake_utf32.ixx b/src/chars/icelake_utf32.ixx deleted file mode 100644 index 697388ee..00000000 --- a/src/chars/icelake_utf32.ixx +++ /dev/null @@ -1,1632 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#if __has_include() -#include -#endif -#if __has_include() -#include -#endif -#include - -export module gal.prometheus.chars:icelake.utf32; - -import std; -import gal.prometheus.meta; -import gal.prometheus.memory; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -import :encoding; -import :scalar.utf32; - -#else -#if __has_include() -#include -#endif -#if __has_include() -#include -#endif -#include - -#include -#include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -#endif - -namespace gal::prometheus::chars -{ - namespace icelake_utf32_detail - { - struct utf16_to_utf8 - { - // 1 byte for length, 16 bytes for mask - constexpr static std::array, 256> _1_2{ - {{16, 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14}, - {15, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80}, - {15, 1, 0, 3, 2, 5, 4, 7, 6, 8, 11, 10, 13, 12, 15, 14, 0x80}, - {14, 0, 3, 2, 5, 4, 7, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, - {15, 1, 0, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80}, - {14, 0, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, - {14, 1, 0, 2, 5, 4, 7, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, - {13, 0, 2, 5, 4, 7, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {15, 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 10, 13, 12, 15, 14, 0x80}, - {14, 0, 3, 2, 5, 4, 7, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80}, - {14, 1, 0, 3, 2, 5, 4, 7, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80}, - {13, 0, 3, 2, 5, 4, 7, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {14, 1, 0, 2, 5, 4, 7, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80}, - {13, 0, 2, 5, 4, 7, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {13, 1, 0, 2, 5, 4, 7, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 2, 5, 4, 7, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {15, 1, 0, 3, 2, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80}, - {14, 0, 3, 2, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, - {14, 1, 0, 3, 2, 4, 7, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, - {13, 0, 3, 2, 4, 7, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {14, 1, 0, 2, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, - {13, 0, 2, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {13, 1, 0, 2, 4, 7, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 2, 4, 7, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {14, 1, 0, 3, 2, 4, 7, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80}, - {13, 0, 3, 2, 4, 7, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 4, 7, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 4, 7, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 2, 4, 7, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 2, 4, 7, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 4, 7, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 4, 7, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {15, 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 12, 15, 14, 0x80}, - {14, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80}, - {14, 1, 0, 3, 2, 5, 4, 7, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80}, - {13, 0, 3, 2, 5, 4, 7, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, - {14, 1, 0, 2, 5, 4, 7, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80}, - {13, 0, 2, 5, 4, 7, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, - {13, 1, 0, 2, 5, 4, 7, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 2, 5, 4, 7, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {14, 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80}, - {13, 0, 3, 2, 5, 4, 7, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 5, 4, 7, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 5, 4, 7, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 2, 5, 4, 7, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 2, 5, 4, 7, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 5, 4, 7, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 5, 4, 7, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {14, 1, 0, 3, 2, 4, 7, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80}, - {13, 0, 3, 2, 4, 7, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 4, 7, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 4, 7, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 2, 4, 7, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 2, 4, 7, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 4, 7, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 4, 7, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 4, 7, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 4, 7, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 3, 2, 4, 7, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 3, 2, 4, 7, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 4, 7, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 4, 7, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 2, 4, 7, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 2, 4, 7, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {15, 1, 0, 3, 2, 5, 4, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80}, - {14, 0, 3, 2, 5, 4, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, - {14, 1, 0, 3, 2, 5, 4, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, - {13, 0, 3, 2, 5, 4, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {14, 1, 0, 2, 5, 4, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, - {13, 0, 2, 5, 4, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {13, 1, 0, 2, 5, 4, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 2, 5, 4, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {14, 1, 0, 3, 2, 5, 4, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80}, - {13, 0, 3, 2, 5, 4, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 5, 4, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 5, 4, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 2, 5, 4, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 2, 5, 4, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 5, 4, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 5, 4, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {14, 1, 0, 3, 2, 4, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80}, - {13, 0, 3, 2, 4, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 4, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 4, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 2, 4, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 2, 4, 6, 9, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 4, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 4, 6, 8, 11, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 4, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 4, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 3, 2, 4, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 3, 2, 4, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 4, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 4, 6, 9, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 2, 4, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 2, 4, 6, 8, 10, 13, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {14, 1, 0, 3, 2, 5, 4, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80}, - {13, 0, 3, 2, 5, 4, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 5, 4, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 5, 4, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 2, 5, 4, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 2, 5, 4, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 5, 4, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 5, 4, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 5, 4, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 5, 4, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 3, 2, 5, 4, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 3, 2, 5, 4, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 5, 4, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 5, 4, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 2, 5, 4, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 2, 5, 4, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 4, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 4, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 3, 2, 4, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 3, 2, 4, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 4, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 4, 6, 9, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 2, 4, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 2, 4, 6, 8, 11, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 3, 2, 4, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 3, 2, 4, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 3, 2, 4, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 3, 2, 4, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 2, 4, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 2, 4, 6, 9, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 1, 0, 2, 4, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 0, 2, 4, 6, 8, 10, 12, 15, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {15, 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 14, 0x80}, - {14, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80}, - {14, 1, 0, 3, 2, 5, 4, 7, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80}, - {13, 0, 3, 2, 5, 4, 7, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, - {14, 1, 0, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80}, - {13, 0, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, - {13, 1, 0, 2, 5, 4, 7, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, - {12, 0, 2, 5, 4, 7, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {14, 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80}, - {13, 0, 3, 2, 5, 4, 7, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 5, 4, 7, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 5, 4, 7, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 2, 5, 4, 7, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80}, - {12, 0, 2, 5, 4, 7, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 5, 4, 7, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 5, 4, 7, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {14, 1, 0, 3, 2, 4, 7, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80}, - {13, 0, 3, 2, 4, 7, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 4, 7, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 4, 7, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 2, 4, 7, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, - {12, 0, 2, 4, 7, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 4, 7, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 4, 7, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 4, 7, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 4, 7, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 3, 2, 4, 7, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 3, 2, 4, 7, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 4, 7, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 4, 7, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 2, 4, 7, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 2, 4, 7, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {14, 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80}, - {13, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 5, 4, 7, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 5, 4, 7, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 2, 5, 4, 7, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80}, - {12, 0, 2, 5, 4, 7, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 5, 4, 7, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 5, 4, 7, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 5, 4, 7, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 3, 2, 5, 4, 7, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 3, 2, 5, 4, 7, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 5, 4, 7, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 5, 4, 7, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 2, 5, 4, 7, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 2, 5, 4, 7, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 4, 7, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 4, 7, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 3, 2, 4, 7, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 3, 2, 4, 7, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 4, 7, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 4, 7, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 2, 4, 7, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 2, 4, 7, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 3, 2, 4, 7, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 3, 2, 4, 7, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 3, 2, 4, 7, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 3, 2, 4, 7, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 2, 4, 7, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 2, 4, 7, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 1, 0, 2, 4, 7, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 0, 2, 4, 7, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {14, 1, 0, 3, 2, 5, 4, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80}, - {13, 0, 3, 2, 5, 4, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 5, 4, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 5, 4, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 2, 5, 4, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, - {12, 0, 2, 5, 4, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 5, 4, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 5, 4, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 5, 4, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 5, 4, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 3, 2, 5, 4, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 3, 2, 5, 4, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 5, 4, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 5, 4, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 2, 5, 4, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 2, 5, 4, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 4, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 4, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 3, 2, 4, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 3, 2, 4, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 4, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 4, 6, 9, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 2, 4, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 2, 4, 6, 8, 11, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 3, 2, 4, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 3, 2, 4, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 3, 2, 4, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 3, 2, 4, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 2, 4, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 2, 4, 6, 9, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 1, 0, 2, 4, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 0, 2, 4, 6, 8, 10, 13, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {13, 1, 0, 3, 2, 5, 4, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80}, - {12, 0, 3, 2, 5, 4, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 3, 2, 5, 4, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 3, 2, 5, 4, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 2, 5, 4, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 2, 5, 4, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 2, 5, 4, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 2, 5, 4, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 3, 2, 5, 4, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 3, 2, 5, 4, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 3, 2, 5, 4, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 3, 2, 5, 4, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 2, 5, 4, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 2, 5, 4, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 1, 0, 2, 5, 4, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 0, 2, 5, 4, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {12, 1, 0, 3, 2, 4, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80}, - {11, 0, 3, 2, 4, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 3, 2, 4, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 3, 2, 4, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 2, 4, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 2, 4, 6, 9, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 1, 0, 2, 4, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 0, 2, 4, 6, 8, 11, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 1, 0, 3, 2, 4, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 3, 2, 4, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 1, 0, 3, 2, 4, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 0, 3, 2, 4, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 1, 0, 2, 4, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 0, 2, 4, 6, 9, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 1, 0, 2, 4, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 0, 2, 4, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}}}; - - // 1 byte for length, 16 bytes for mask - constexpr static std::array, 256> _1_2_3{ - {{12, 2, 3, 1, 6, 7, 5, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80}, - {9, 6, 7, 5, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 3, 1, 6, 7, 5, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 0, 6, 7, 5, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 2, 3, 1, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 3, 1, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 0, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 2, 3, 1, 7, 5, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 7, 5, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 3, 1, 7, 5, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 0, 7, 5, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 2, 3, 1, 4, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 4, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 3, 1, 4, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 0, 4, 10, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 2, 3, 1, 6, 7, 5, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 6, 7, 5, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 3, 1, 6, 7, 5, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 0, 6, 7, 5, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 2, 3, 1, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 3, 1, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 0, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 2, 3, 1, 7, 5, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 7, 5, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 3, 1, 7, 5, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 0, 7, 5, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 2, 3, 1, 4, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 4, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 3, 1, 4, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 0, 4, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 2, 3, 1, 6, 7, 5, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 6, 7, 5, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 3, 1, 6, 7, 5, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 0, 6, 7, 5, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 2, 3, 1, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 3, 1, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 0, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 2, 3, 1, 7, 5, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 7, 5, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 3, 1, 7, 5, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 0, 7, 5, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 2, 3, 1, 4, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 4, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 3, 1, 4, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 0, 4, 11, 9, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 2, 3, 1, 6, 7, 5, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 6, 7, 5, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 3, 1, 6, 7, 5, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 0, 6, 7, 5, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 2, 3, 1, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 3, 1, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 0, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 2, 3, 1, 7, 5, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 7, 5, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 3, 1, 7, 5, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 0, 7, 5, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 2, 3, 1, 4, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 4, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 3, 1, 4, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 0, 4, 8, 14, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 2, 3, 1, 6, 7, 5, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 6, 7, 5, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 3, 1, 6, 7, 5, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 0, 6, 7, 5, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 2, 3, 1, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 3, 1, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 0, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 2, 3, 1, 7, 5, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 7, 5, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 3, 1, 7, 5, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 0, 7, 5, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 2, 3, 1, 4, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 4, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 3, 1, 4, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 0, 4, 10, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 2, 3, 1, 6, 7, 5, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 6, 7, 5, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 3, 1, 6, 7, 5, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 0, 6, 7, 5, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 2, 3, 1, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {2, 3, 1, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {1, 0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 2, 3, 1, 7, 5, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {2, 7, 5, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 3, 1, 7, 5, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 0, 7, 5, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 2, 3, 1, 4, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {1, 4, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 3, 1, 4, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {2, 0, 4, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 2, 3, 1, 6, 7, 5, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 6, 7, 5, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 3, 1, 6, 7, 5, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 0, 6, 7, 5, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 2, 3, 1, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {2, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 3, 1, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 0, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 2, 3, 1, 7, 5, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 7, 5, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 3, 1, 7, 5, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 0, 7, 5, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 2, 3, 1, 4, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 4, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 3, 1, 4, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 0, 4, 11, 9, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 2, 3, 1, 6, 7, 5, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 6, 7, 5, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 3, 1, 6, 7, 5, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 0, 6, 7, 5, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 2, 3, 1, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {1, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 3, 1, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {2, 0, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 2, 3, 1, 7, 5, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 7, 5, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 3, 1, 7, 5, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 0, 7, 5, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 2, 3, 1, 4, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {2, 4, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 3, 1, 4, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 0, 4, 8, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {11, 2, 3, 1, 6, 7, 5, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 6, 7, 5, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 3, 1, 6, 7, 5, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 0, 6, 7, 5, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 2, 3, 1, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 3, 1, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 0, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 2, 3, 1, 7, 5, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 7, 5, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 3, 1, 7, 5, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 0, 7, 5, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 2, 3, 1, 4, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 4, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 3, 1, 4, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 0, 4, 10, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 2, 3, 1, 6, 7, 5, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 6, 7, 5, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 3, 1, 6, 7, 5, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 0, 6, 7, 5, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 2, 3, 1, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {2, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 3, 1, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 0, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 2, 3, 1, 7, 5, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 7, 5, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 3, 1, 7, 5, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 0, 7, 5, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 2, 3, 1, 4, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 4, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 3, 1, 4, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 0, 4, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 2, 3, 1, 6, 7, 5, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 6, 7, 5, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 3, 1, 6, 7, 5, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 0, 6, 7, 5, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 2, 3, 1, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 3, 1, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 0, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 2, 3, 1, 7, 5, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 7, 5, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 3, 1, 7, 5, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 0, 7, 5, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 2, 3, 1, 4, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 4, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 3, 1, 4, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 0, 4, 11, 9, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 2, 3, 1, 6, 7, 5, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 6, 7, 5, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 3, 1, 6, 7, 5, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 0, 6, 7, 5, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 2, 3, 1, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 3, 1, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 0, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 2, 3, 1, 7, 5, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 7, 5, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 3, 1, 7, 5, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 0, 7, 5, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 2, 3, 1, 4, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 4, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 3, 1, 4, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 0, 4, 8, 15, 13, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {10, 2, 3, 1, 6, 7, 5, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 6, 7, 5, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 3, 1, 6, 7, 5, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 0, 6, 7, 5, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 2, 3, 1, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 3, 1, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 0, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 2, 3, 1, 7, 5, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 7, 5, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 3, 1, 7, 5, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 0, 7, 5, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 2, 3, 1, 4, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 4, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 3, 1, 4, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 0, 4, 10, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 2, 3, 1, 6, 7, 5, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 6, 7, 5, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 3, 1, 6, 7, 5, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 0, 6, 7, 5, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 2, 3, 1, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {1, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 3, 1, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {2, 0, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 2, 3, 1, 7, 5, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 7, 5, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 3, 1, 7, 5, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 0, 7, 5, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 2, 3, 1, 4, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {2, 4, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 3, 1, 4, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 0, 4, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {9, 2, 3, 1, 6, 7, 5, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 6, 7, 5, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 3, 1, 6, 7, 5, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 0, 6, 7, 5, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 2, 3, 1, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 3, 1, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 0, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 2, 3, 1, 7, 5, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 7, 5, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 3, 1, 7, 5, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 0, 7, 5, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 2, 3, 1, 4, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 4, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 3, 1, 4, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 0, 4, 11, 9, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {8, 2, 3, 1, 6, 7, 5, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 6, 7, 5, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 3, 1, 6, 7, 5, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 0, 6, 7, 5, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 2, 3, 1, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {2, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 3, 1, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 0, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {7, 2, 3, 1, 7, 5, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 7, 5, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 3, 1, 7, 5, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 0, 7, 5, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {6, 2, 3, 1, 4, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {3, 4, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {5, 3, 1, 4, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}, - {4, 0, 4, 8, 12, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}}}; - }; - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - - template<> - class Simd<"icelake.utf32"> - { - public: - using scalar_type = Scalar<"utf32">; - - constexpr static auto input_category = scalar_type::input_category; - using input_type = scalar_type::input_type; - using char_type = scalar_type::char_type; - using pointer_type = scalar_type::pointer_type; - using size_type = scalar_type::size_type; - - template - [[nodiscard]] constexpr static auto validate(const input_type input) noexcept -> std::conditional_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - if constexpr (ReturnResultType) - { - for (; it_input_current + 16 <= it_input_end; it_input_current += 16) - { - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - const auto in = _mm512_loadu_si512(it_input_current); - const auto offset = _mm512_add_epi32(in, _mm512_set1_epi32(static_cast(0xffff'2000))); - - const auto outside_range = _mm512_cmp_epu32_mask(in, _mm512_set1_epi32(0x0010'ffff), _MM_CMPINT_GT); - const auto surrogate_range = _mm512_cmp_epu32_mask(offset, _mm512_set1_epi32(static_cast(0xffff'f7ff)), _MM_CMPINT_GT); - - if (outside_range | surrogate_range) - { - const auto outside_index = std::countr_zero(outside_range); - const auto surrogate_index = std::countr_zero(surrogate_range); - - if (outside_range < surrogate_range) - { - return result_type{.error = ErrorCode::TOO_LARGE, .count = length_if_error + outside_index}; - } - return result_type{.error = ErrorCode::SURROGATE, .count = length_if_error + surrogate_index}; - } - } - - if (it_input_current < it_input_end) - { - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - const auto in = _mm512_maskz_loadu_epi32( - static_cast<__mmask16>((__mmask16{1} << (it_input_end - it_input_current)) - 1), - it_input_current - ); - const auto offset = _mm512_add_epi32(in, _mm512_set1_epi32(static_cast(0xffff'2000))); - - const auto outside_range = _mm512_cmp_epu32_mask(in, _mm512_set1_epi32(0x10'ffff), _MM_CMPINT_GT); - const auto surrogate_range = _mm512_cmp_epu32_mask(offset, _mm512_set1_epi32(static_cast(0xffff'f7ff)), _MM_CMPINT_GT); - - if (outside_range | surrogate_range) - { - const auto outside_index = std::countr_zero(outside_range); - const auto surrogate_index = std::countr_zero(surrogate_range); - - if (outside_range < surrogate_range) - { - return result_type{.error = ErrorCode::TOO_LARGE, .count = length_if_error + outside_index}; - } - return result_type{.error = ErrorCode::SURROGATE, .count = length_if_error + surrogate_index}; - } - } - - return result_type{.error = ErrorCode::NONE, .count = input_length}; - } - else - { - const auto offset = _mm512_set1_epi32(static_cast(0xffff'2000)); - const auto standard_max = _mm512_set1_epi32(0x0010'ffff); - const auto standard_offset_max = _mm512_set1_epi32(static_cast(0xffff'f7ff)); - - auto current_max = _mm512_setzero_si512(); - auto current_offset_max = _mm512_setzero_si512(); - - for (; it_input_current + 16 <= it_input_end; it_input_current += 16) - { - const auto utf32 = _mm512_loadu_si512(it_input_current); - - current_max = _mm512_max_epu32(utf32, current_max); - current_offset_max = _mm512_max_epu32(_mm512_add_epi32(utf32, offset), current_offset_max); - } - - if (const auto is_zero = _mm512_xor_si512(_mm512_max_epu32(current_max, standard_max), standard_max); - _mm512_test_epi8_mask(is_zero, is_zero) != 0) { return false; } - - if (const auto is_zero = _mm512_xor_si512(_mm512_max_epu32(current_offset_max, standard_offset_max), standard_offset_max); - _mm512_test_epi8_mask(is_zero, is_zero) != 0) { return false; } - - return static_cast(scalar_type::validate({it_input_current, input_length - (it_input_current - it_input_begin)})); - } - } - - template - [[nodiscard]] constexpr static auto validate(const pointer_type input) noexcept -> std::conditional_t - { - return validate({input, std::char_traits::length(input)}); - } - - // note: we are not BOM aware - template - [[nodiscard]] constexpr static auto length(const input_type input) noexcept -> size_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - if constexpr (OutputCategory == CharsCategory::ASCII) { return input.size(); } // NOLINT(bugprone-branch-clone) - else if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) - { - const auto v_007f = _mm512_set1_epi32(0x0007f); - const auto v_07ff = _mm512_set1_epi32(0x07ff); - const auto v_ffff = _mm512_set1_epi32(0xffff); - - auto result_length = static_cast(0); - - for (; it_input_current + 16 <= it_input_end; it_input_current += 16) - { - const auto utf32 = _mm512_loadu_si512(it_input_current); - - const auto ascii_bitmask = _mm512_cmple_epu32_mask(utf32, v_007f); - const auto two_bytes_bitmask = _mm512_mask_cmple_epu32_mask(_knot_mask16(ascii_bitmask), utf32, v_07ff); - const auto three_bytes_bitmask = _mm512_mask_cmple_epu32_mask( - _knot_mask16(_mm512_kor(ascii_bitmask, two_bytes_bitmask)), - utf32, - v_ffff - ); - - const auto ascii_count = std::popcount(ascii_bitmask); - const auto two_bytes_count = std::popcount(two_bytes_bitmask); - const auto three_bytes_count = std::popcount(three_bytes_bitmask); - const auto four_bytes_count = 16 - ascii_count - two_bytes_count - three_bytes_count; - - result_length += ascii_count + 2 * two_bytes_count + 3 * three_bytes_count + 4 * four_bytes_count; - } - - return result_length + scalar_type::length({it_input_current, input_length - (it_input_current - it_input_begin)}); - } - else if constexpr (OutputCategory == CharsCategory::UTF16_LE or OutputCategory == CharsCategory::UTF16_BE or OutputCategory == CharsCategory::UTF16) - { - const auto v_ffff = _mm512_set1_epi32(0x0000'ffff); - - auto result_length = static_cast(0); - - for (; it_input_current + 16 <= it_input_end; it_input_current += 16) - { - const auto utf32 = _mm512_loadu_si512(it_input_current); - const auto surrogates_bitmask = _mm512_cmpgt_epu32_mask(utf32, v_ffff); - - result_length += 16 + std::popcount(surrogates_bitmask); - } - - return result_length + scalar_type::length({it_input_current, input_length - (it_input_current - it_input_begin)}); - } - else if constexpr (OutputCategory == CharsCategory::UTF32) { return input.size(); } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - // note: we are not BOM aware - template - [[nodiscard]] constexpr static auto length(const pointer_type input) noexcept -> size_type - { - return length({input, std::char_traits::length(input)}); - } - - // fixme: haswell implementation for now - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert( - const input_type input, - typename output_type::pointer output - ) noexcept -> std::conditional_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); - if constexpr (ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(validate(input)); - } - - using output_pointer_type = typename output_type::pointer; - using output_char_type = typename output_type::value_type; - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - const output_pointer_type it_output_begin = output; - output_pointer_type it_output_current = it_output_begin; - - if constexpr (OutputCategory == CharsCategory::ASCII) - { - const auto v_00ff = _mm512_set1_epi32(0x0000'00ff); - const auto shuffle_mask = _mm512_set_epi8( - // clang-format off - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 60, - 56, - 52, - 48, - 44, - 40, - 36, - 32, - 28, - 24, - 20, - 16, - 12, - 8, - 4, - 0 - // clang-format on - ); - - for (; it_input_current + 16 <= it_input_end; it_input_current += 16, it_output_current += 16) - { - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - const auto in = _mm512_loadu_si512(it_input_current); - - if (_mm512_cmpgt_epu32_mask(in, v_00ff)) - { - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - const auto it = std::ranges::find_if( - it_input_current, - it_input_current + 16, - [](const auto data) noexcept { return data > 0xff; }); - // fixme: write or ignore? - { - std::ranges::transform( - std::ranges::subrange{it_input_current, it}, - it_output_current, - [](const auto word) noexcept -> output_char_type - { - return static_cast(word); - } - ); - it_output_current += std::ranges::distance(it_input_current, it); - } - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it != it_input_current + 16); - return result_type{.error = ErrorCode::TOO_LARGE, .count = length_if_error + std::ranges::distance(it_input_current, it)}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - _mm_storeu_si128( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m128i*, it_output_current), - _mm512_castsi512_si128(_mm512_permutexvar_epi8(shuffle_mask, in)) - ); - } - - if (const auto remaining = it_input_end - it_input_current; - remaining != 0) - { - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - const auto mask = static_cast<__mmask16>((1 << remaining) - 1); - const auto in = _mm512_maskz_loadu_epi32(mask, it_input_current); - - if (_mm512_cmpgt_epu32_mask(in, v_00ff)) - { - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - const auto it = std::ranges::find_if( - it_input_current, - it_input_current + 16, - [](const auto data) noexcept { return data > 0xff; }); - // fixme: write or ignore? - { - std::ranges::transform( - std::ranges::subrange{it_input_current, it}, - it_output_current, - [](const auto word) noexcept -> output_char_type - { - return static_cast(word); - } - ); - it_output_current += std::ranges::distance(it_input_current, it); - } - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it != it_input_current + 16); - return result_type{.error = ErrorCode::TOO_LARGE, .count = length_if_error + std::ranges::distance(it_input_current, it)}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - _mm_mask_storeu_epi8(it_output_current, mask, _mm512_castsi512_si128(_mm512_permutexvar_epi8(shuffle_mask, in))); - it_input_current += remaining; - it_output_current += remaining; - } - } - else if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) - { - const auto process = [&]() noexcept -> std::conditional_t - { - const auto v_0010_ffff = _mm256_set1_epi32(0x0010'ffff); - const auto v_7fff_ffff = _mm256_set1_epi32(0x7fff'ffff); - const auto v_0000_ff80 = _mm256_set1_epi16(static_cast(0x0000'ff80)); - const auto v_0000_0000 = _mm256_setzero_si256(); - const auto v_0000_f800 = _mm256_set1_epi16(static_cast(0x0000'f800)); - const auto v_0000_1f00 = _mm256_set1_epi16(0x0000'1f00); - const auto v_0000_003f = _mm256_set1_epi16(0x0000'003f); - const auto v_0000_c080 = _mm256_set1_epi16(static_cast(0x0000'c080)); - const auto v_ffff_0000 = _mm256_set1_epi32(static_cast(0xffff'0000)); - const auto v_0000_d800 = _mm256_set1_epi16(static_cast(0x0000'd800)); - - // to avoid overruns - constexpr auto safety_margin = 12; - - // used iff ProcessPolicy != InputProcessPolicy::RETURN_RESULT_TYPE - auto running_max = _mm256_setzero_si256(); - auto forbidden_byte_mask = _mm256_setzero_si256(); - - while (it_input_current + 16 + safety_margin <= it_input_end) - { - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - const auto in = _mm256_loadu_si256( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST( - const __m256i*, - it_input_current - ) + 0 - ); - const auto next_in = _mm256_loadu_si256( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST( - const __m256i*, - it_input_current - ) + 1 - ); - - if constexpr ( - ProcessPolicy != InputProcessPolicy::RETURN_RESULT_TYPE or ProcessPolicy == - InputProcessPolicy::ASSUME_VALID_INPUT - ) { running_max = _mm256_max_epu32(_mm256_max_epu32(in, running_max), next_in); } - else - { - // Check for too large input - if (const auto max_input = _mm256_max_epu32(_mm256_max_epu32(in, next_in), v_0010_ffff); - static_cast(_mm256_movemask_epi8(_mm256_cmpeq_epi32(max_input, v_0010_ffff))) != 0xffff'ffff) - { - return result_type{.error = ErrorCode::TOO_LARGE, .count = length_if_error}; - } - } - - // Pack 32-bit UTF-32 code units to 16-bit UTF-16 code units with unsigned saturation - const auto in_16 = _mm256_permute4x64_epi64( - _mm256_packus_epi32(_mm256_and_si256(in, v_7fff_ffff), _mm256_and_si256(next_in, v_7fff_ffff)), - 0b1101'1000 - ); - - // Try to apply UTF-16 => UTF-8 routine on 256 bits - if (_mm256_testz_si256(in_16, v_0000_ff80)) - { - // ascii - // pack the bytes - const auto utf8_packed = _mm_packus_epi16(_mm256_castsi256_si128(in_16), _mm256_extractf128_si256(in_16, 1)); - // store 16 bytes - _mm_storeu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m128i*, it_output_current), utf8_packed); - // adjust iterators - it_input_current += 16; - it_output_current += 16; - // we are done for this round - continue; - } - - // no bits set above 7th bit - const auto one_byte_byte_mask = _mm256_cmpeq_epi16(_mm256_and_si256(in_16, v_0000_ff80), v_0000_0000); - const auto one_byte_bitmask = static_cast(_mm256_movemask_epi8(one_byte_byte_mask)); - - // no bits set above 11th bit - const auto one_or_two_bytes_byte_mask = _mm256_cmpeq_epi16(_mm256_and_si256(in_16, v_0000_f800), v_0000_0000); - const auto one_or_two_bytes_bitmask = static_cast(_mm256_movemask_epi8(one_or_two_bytes_byte_mask)); - - if (one_or_two_bytes_bitmask == 0xffff'ffff) - { - // 1.prepare 2-bytes values - // input 16-bit word : [0000|0aaa|aabb|bbbb] x 8 - // expected output : [110a|aaaa|10bb|bbbb] x 8 - - // t0 = [000a|aaaa|bbbb|bb00] - const auto t0 = _mm256_slli_epi16(in_16, 2); - // t1 = [000a|aaaa|0000|0000] - const auto t1 = _mm256_and_si256(t0, v_0000_1f00); - // t2 = [0000|0000|00bb|bbbb] - const auto t2 = _mm256_and_si256(in_16, v_0000_003f); - // t3 = [000a|aaaa|00bb|bbbb] - const auto t3 = _mm256_or_si256(t1, t2); - // t4 = [110a|aaaa|10bb|bbbb] - const auto t4 = _mm256_or_si256(t3, v_0000_c080); - - // 2.merge ascii and 2-bytes codewords - const auto utf8_unpacked = _mm256_blendv_epi8(t4, in_16, one_byte_byte_mask); - - // 3.prepare bitmask for 8-bits lookup - const auto mask_0 = static_cast(one_byte_bitmask & 0x5555'5555); - const auto mask_1 = static_cast(mask_0 >> 7); - const auto mask = static_cast((mask_0 | mask_1) & 0x00ff'00ff); - - // 4.pack the bytes - const auto length_0 = icelake_utf32_detail::utf16_to_utf8::_1_2[static_cast(mask)].front(); - const auto* row_0 = icelake_utf32_detail::utf16_to_utf8::_1_2[static_cast(mask)].data() + 1; - const auto length_1 = icelake_utf32_detail::utf16_to_utf8::_1_2[static_cast(mask >> 16)].front(); - const auto* row_1 = icelake_utf32_detail::utf16_to_utf8::_1_2[static_cast(mask >> 16)].data() + 1; - - const auto shuffle_0 = _mm_loadu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m128i*, row_0)); - const auto shuffle_1 = _mm_loadu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m128i*, row_1)); - - const auto utf8_packed = _mm256_shuffle_epi8(utf8_unpacked, _mm256_setr_m128i(shuffle_0, shuffle_1)); - - // 5.store the bytes - _mm_storeu_si128( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m128i*, it_output_current), - _mm256_castsi256_si128(utf8_packed) - ); - it_output_current += length_0; - _mm_storeu_si128( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m128i*, it_output_current), - _mm256_extractf128_si256(utf8_packed, 1) - ); - it_output_current += length_1; - - // 6.adjust iterators - it_input_current += 16; - // we are done for this round - continue; - } - - // check for overflow in packing - const auto saturation_byte_mask = - _mm256_cmpeq_epi32(_mm256_and_si256(_mm256_or_si256(in, next_in), v_ffff_0000), v_0000_0000); - const auto saturation_bitmask = static_cast(_mm256_movemask_epi8(saturation_byte_mask)); - - if (saturation_bitmask == 0xffff'ffff) - { - // code units from register produce either 1, 2 or 3 UTF-8 bytes - - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) - { - forbidden_byte_mask = _mm256_or_si256(forbidden_byte_mask, - _mm256_cmpeq_epi16(_mm256_and_si256(in_16, v_0000_f800), v_0000_d800)); - } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - forbidden_byte_mask = _mm256_cmpeq_epi16(_mm256_and_si256(in_16, v_0000_f800), v_0000_d800); - if (static_cast(_mm256_movemask_epi8(forbidden_byte_mask)) != 0x0) - { - return result_type{.error = ErrorCode::SURROGATE, .count = length_if_error}; - } - } - - // In this branch we handle three cases: - // 1. [0000|0000|0ccc|cccc] => [0ccc|cccc] - single UFT-8 byte - // 2. [0000|0bbb|bbcc|cccc] => [110b|bbbb], [10cc|cccc] - two UTF-8 bytes - // 3. [aaaa|bbbb|bbcc|cccc] => [1110|aaaa], [10bb|bbbb], [10cc|cccc] - three UTF-8 bytes - - // We expand the input word (16-bit) into two code units (32-bit), thus we have room for four bytes. - // However, we need five distinct bit layouts. Note that the last byte in cases #2 and #3 is the same. - - // We precompute byte 1 for case #1 and the common byte for cases #2 and #3 in register t2. - // We precompute byte 1 for case #3 and -- **conditionally** -- precompute either byte 1 for case #2 or byte 2 for case #3. Note that they differ by exactly one bit. - // Finally from these two code units we build proper UTF-8 sequence, taking into account the case (i.e, the number of bytes to write). - - // input : [aaaa|bbbb|bbcc|cccc] - // output : - // t2 => [0ccc|cccc] [10cc|cccc] - // s4 => [1110|aaaa] ([110b|bbbb] or [10bb|bbbb]) - - // clang-format off - const auto dup_even = _mm256_setr_epi16( - 0x0000, - 0x0202, - 0x0404, - 0x0606, - 0x0808, - 0x0a0a, - 0x0c0c, - 0x0e0e, - 0x0000, - 0x0202, - 0x0404, - 0x0606, - 0x0808, - 0x0a0a, - 0x0c0c, - 0x0e0e - ); - // clang-format on - - // [aaaa|bbbb|bbcc|cccc] => [bbcc|cccc|bbcc|cccc] - const auto t0 = _mm256_shuffle_epi8(in_16, dup_even); - // [bbcc|cccc|bbcc|cccc] => [00cc|cccc|0bcc|cccc] - const auto t1 = _mm256_and_si256(t0, _mm256_set1_epi16(0b0011'1111'0111'1111)); - // [00cc|cccc|0bcc|cccc] => [10cc|cccc|0bcc|cccc] - const auto t2 = _mm256_or_si256(t1, _mm256_set1_epi16(static_cast(0b1000'0000'0000'0000))); - - // [aaaa|bbbb|bbcc|cccc] => [0000|aaaa|bbbb|bbcc] - const auto s0 = _mm256_srli_epi16(in_16, 4); - // [0000|aaaa|bbbb|bbcc] => [0000|aaaa|bbbb|bb00] - const auto s1 = _mm256_and_si256(s0, _mm256_set1_epi16(0b0000'1111'1111'1100)); - // [0000|aaaa|bbbb|bb00] => [00bb|bbbb|0000|aaaa] - const auto s2 = _mm256_maddubs_epi16(s1, _mm256_set1_epi16(0x0140)); - // [00bb|bbbb|0000|aaaa] => [11bb|bbbb|1110|aaaa] - const auto s3 = _mm256_or_si256(s2, _mm256_set1_epi16(static_cast(0b1100'0000'1110'0000))); - const auto s4 = _mm256_xor_si256( - s3, - _mm256_andnot_si256(one_or_two_bytes_byte_mask, _mm256_set1_epi16(0b0100'0000'0000'0000)) - ); - - // expand code units 16-bit => 32-bit - const auto out_0 = _mm256_unpacklo_epi16(t2, s4); - const auto out_1 = _mm256_unpackhi_epi16(t2, s4); - // compress 32-bit code units into 1, 2 or 3 bytes -- 2 x shuffle - const auto mask = - (one_byte_bitmask & 0x5555'5555) | - (one_or_two_bytes_bitmask & 0xaaaa'aaaa); - - const auto length_0 = icelake_utf32_detail::utf16_to_utf8::_1_2_3[static_cast(mask)].front(); - const auto* row_0 = icelake_utf32_detail::utf16_to_utf8::_1_2_3[static_cast(mask)].data() + 1; - const auto length_1 = icelake_utf32_detail::utf16_to_utf8::_1_2_3[static_cast(mask >> 8)].front(); - const auto* row_1 = icelake_utf32_detail::utf16_to_utf8::_1_2_3[static_cast(mask >> 8)].data() + 1; - const auto length_2 = icelake_utf32_detail::utf16_to_utf8::_1_2_3[static_cast(mask >> 16)].front(); - const auto* row_2 = icelake_utf32_detail::utf16_to_utf8::_1_2_3[static_cast(mask >> 16)].data() + 1; - const auto length_3 = icelake_utf32_detail::utf16_to_utf8::_1_2_3[static_cast(mask >> 24)].front(); - const auto* row_3 = icelake_utf32_detail::utf16_to_utf8::_1_2_3[static_cast(mask >> 24)].data() + 1; - - const auto shuffle_0 = _mm_loadu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m128i*, row_0)); - const auto shuffle_1 = _mm_loadu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m128i*, row_1)); - const auto shuffle_2 = _mm_loadu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m128i*, row_2)); - const auto shuffle_3 = _mm_loadu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m128i*, row_3)); - - const auto utf8_0 = _mm_shuffle_epi8(_mm256_castsi256_si128(out_0), shuffle_0); - const auto utf8_1 = _mm_shuffle_epi8(_mm256_castsi256_si128(out_1), shuffle_1); - const auto utf8_2 = _mm_shuffle_epi8(_mm256_extractf128_si256(out_0, 1), shuffle_2); - const auto utf8_3 = _mm_shuffle_epi8(_mm256_extractf128_si256(out_1, 1), shuffle_3); - - _mm_storeu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m128i*, it_output_current), utf8_0); - it_output_current += length_0; - _mm_storeu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m128i*, it_output_current), utf8_1); - it_output_current += length_1; - _mm_storeu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m128i*, it_output_current), utf8_2); - it_output_current += length_2; - _mm_storeu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m128i*, it_output_current), utf8_3); - it_output_current += length_3; - - it_input_current += 16; - } - else - { - // at least one 32-bit word is larger than 0xffff <=> it will produce four UTF-8 bytes - // scalar fallback - // const auto step = static_cast(it_input_end - it_input_current - 1 < 15 ? it_input_end - it_input_current - 1 : 15); - // auto result = scalar_type::convert({it_input_current, step}, it_output_current); - // we don't currently have a way to get the length of the input read and output write at the same time (especially with the possibility of errors), - // so we inline it in our code (chars/scalar_utf32.ixx). - const auto step = static_cast(it_input_end - it_input_current - 1 < 15 ? it_input_end - it_input_current - 1 : 15); - for (const auto it_this_input_end = it_input_current + step; it_input_current < it_this_input_end; ++it_input_current) - { - if (const auto word = *it_input_current; - (word & 0xffff'ff80) == 0) - { - // 1-byte ascii - *(it_output_current + 0) = static_cast(word); - it_output_current += 1; - } - else if ((word & 0xffff'f800) == 0) - { - // 2-bytes utf8 - // 0b110?'???? 0b10??'???? - *(it_output_current + 0) = static_cast((word >> 6) | 0b1100'0000); - *(it_output_current + 1) = static_cast((word & 0b0011'1111) | 0b1000'0000); - it_output_current += 2; - } - else if ((word & 0xffff'0000) == 0) - { - // 3-bytes utf8 - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if (word >= 0xd800 and word <= 0xdfff) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::SURROGATE, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - } - // 0b1110'???? 0b10??'???? 0b10??'???? - *(it_output_current + 0) = static_cast((word >> 12) | 0b1110'0000); - *(it_output_current + 1) = static_cast(((word >> 6) & 0b0011'1111) | 0b1000'0000); - *(it_output_current + 2) = static_cast((word & 0b0011'1111) | 0b1000'0000); - it_output_current += 3; - } - else - { - // 4-bytes utf8 - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if (word > 0x0010'ffff) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::TOO_LARGE, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - } - // 0b1111'0??? 0b10??'???? 0b10??'???? 0b10??'???? - *(it_output_current + 0) = static_cast((word >> 18) | 0b1111'0000); - *(it_output_current + 1) = static_cast(((word >> 12) & 0b0011'1111) | 0b1000'0000); - *(it_output_current + 2) = static_cast(((word >> 6) & 0b0011'1111) | 0b1000'0000); - *(it_output_current + 3) = static_cast((word & 0b0011'1111) | 0b1000'0000); - it_output_current += 4; - } - } - } - } - - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) - { - // check for invalid input - if (static_cast( - _mm256_movemask_epi8(_mm256_cmpeq_epi32(_mm256_max_epu32(running_max, v_0010_ffff), v_0010_ffff)) - ) != 0xffff'ffff) { return false; } - if (static_cast(_mm256_movemask_epi8(forbidden_byte_mask)) != 0x0) { return false; } - - return true; - } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::NONE, .count = static_cast(it_input_current - it_input_begin)}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - }; - - const auto result = process(); - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { if (not result) { return 0; } } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) { if (not result) { return result; } } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - - if (const auto remaining = static_cast(it_input_end - it_input_current); - remaining != 0) - { - if (const auto scalar_result = scalar_type::convert( - {it_input_current, remaining}, - it_output_current - ); - not scalar_result) - { - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = scalar_result.error, .count = result.count + scalar_result.count}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - else - { - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) - { - it_output_current += scalar_result; - } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - it_input_current += scalar_result.count; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - } - } - else if constexpr (OutputCategory == CharsCategory::UTF16_LE or OutputCategory == CharsCategory::UTF16_BE) - { - const auto process = [&]() noexcept -> std::conditional_t - { - const auto v_ffff_0000 = _mm256_set1_epi32(static_cast(0xffff'0000)); - const auto v_0000_0000 = _mm256_setzero_si256(); - const auto v_0000_f800 = _mm256_set1_epi16(static_cast(0x0000'f800)); - const auto v_0000_d800 = _mm256_set1_epi16(static_cast(0x0000'd800)); - - // to avoid overruns - constexpr auto safety_margin = 12; - - // used iff ProcessPolicy != InputProcessPolicy::RETURN_RESULT_TYPE - auto forbidden_byte_mask = _mm256_setzero_si256(); - - while (it_input_current + 8 + safety_margin <= it_input_end) - { - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - const auto in = _mm256_loadu_si256(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m256i*, it_input_current + 0)); - - // no bits set above 16th bit <=> can pack to UTF16 without surrogate pairs - const auto saturation_byte_mask = _mm256_cmpeq_epi32(_mm256_and_si256(in, v_ffff_0000), v_0000_0000); - const auto saturation_bitmask = static_cast(_mm256_movemask_epi8(saturation_byte_mask)); - - if (saturation_bitmask == 0xffff'ffff) - { - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) - { - forbidden_byte_mask = _mm256_or_si256(forbidden_byte_mask, - _mm256_cmpeq_epi32(_mm256_and_si256(in, v_0000_f800), v_0000_d800)); - } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - forbidden_byte_mask = _mm256_cmpeq_epi32(_mm256_and_si256(in, v_0000_f800), v_0000_d800); - if (static_cast(_mm256_movemask_epi8(forbidden_byte_mask)) != 0x0) - { - return result_type{.error = ErrorCode::SURROGATE, .count = length_if_error}; - } - } - - const auto utf16_packed = [ - packed = _mm_packus_epi32(_mm256_castsi256_si128(in), _mm256_extractf128_si256(in, 1)) - ]() noexcept - { - if constexpr ((OutputCategory == CharsCategory::UTF16_LE) != (std::endian::native == std::endian::little)) - { - return _mm_shuffle_epi8(packed, _mm_setr_epi8(1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14)); - } - else { return packed; } - }(); - - _mm_storeu_si128(GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m128i*, it_output_current), utf16_packed); - - it_input_current += 8; - it_output_current += 8; - } - else - { - // scalar fallback - // const auto step = static_cast(it_input_end - it_input_current - 1 < 7 ? it_input_end - it_input_current - 1 : 7); - // auto result = scalar_type::convert({it_input_current, step}, it_output_current); - // we don't currently have a way to get the length of the input read and output write at the same time (especially with the possibility of errors), - // so we inline it in our code (chars/scalar_utf32.ixx). - const auto step = static_cast(it_input_end - it_input_current - 1 < 7 ? it_input_end - it_input_current - 1 : 7); - for (const auto it_this_input_end = it_input_current + step; it_input_current < it_this_input_end; ++it_input_current) - { - if (const auto word = *it_input_current; - (word & 0xffff'0000) == 0) - { - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if (word >= 0xd800 and word <= 0xdfff) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::SURROGATE, .count = length_if_error}; - } - } - } - const auto real_word = [w = static_cast(word)]() noexcept - { - if constexpr ((OutputCategory == CharsCategory::UTF16_LE) != (std::endian::native == std::endian::little)) - { - return std::byteswap(w); - } - else { return w; } - }(); - - *(it_output_current + 0) = static_cast(real_word); - it_output_current += 1; - } - else - { - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if (word > 0x0010'ffff) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::TOO_LARGE, .count = length_if_error}; - } - } - const auto [high_surrogate, low_surrogate] = [real_word = word - 0x0001'0000]() noexcept - { - const auto high = 0xd800 + (real_word >> 10); - const auto low = 0xdc00 + (real_word & 0x3ff); - if constexpr ((OutputCategory == CharsCategory::UTF16_LE) != (std::endian::native == std::endian::little)) - { - return std::make_pair(std::byteswap(high), std::byteswap(low)); - } - else { return std::make_pair(high, low); } - }(); - - *(it_output_current + 0) = static_cast(high_surrogate); - *(it_output_current + 1) = static_cast(low_surrogate); - it_output_current += 2; - } - } - } - } - } - - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) - { - // check for invalid input - if (static_cast(_mm256_movemask_epi8(forbidden_byte_mask)) != 0x0) { return false; } - - return true; - } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::NONE, .count = static_cast(it_input_current - it_input_begin)}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - }; - - const auto result = process(); - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { if (not result) { return 0; } } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) { if (not result) { return result; } } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - - if (const auto remaining = static_cast(it_input_end - it_input_current); - remaining != 0) - { - if (const auto scalar_result = scalar_type::convert( - {it_input_current, remaining}, - it_output_current - ); - not scalar_result) - { - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = scalar_result.error, .count = result.count + scalar_result.count}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - else - { - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { it_output_current += scalar_result; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) { it_input_current += scalar_result.count; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - } - } - else if constexpr (OutputCategory == CharsCategory::UTF32) - { - std::memcpy(it_output_current, it_input_current, input_length * sizeof(char_type)); - it_input_current += input_length; - it_output_current += input_length; - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE("Unknown or unsupported `OutputCategory` (we don't know the `endian` by UTF16, so it's not allowed to use it here)."); - } - - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { return static_cast(it_output_current - it_output_begin); } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::NONE, .count = static_cast(it_input_current - it_input_begin)}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert( - const pointer_type input, - typename output_type::pointer output - ) noexcept -> std::conditional_t - { - return convert({input, std::char_traits::length(input)}, output); - } - - template< - typename StringType, - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - requires requires(StringType& string) - { - string.resize(std::declval()); - { - string.data() - } -> std::convertible_to::pointer>; - } - [[nodiscard]] constexpr static auto convert(const input_type input) noexcept -> StringType - { - StringType result{}; - result.resize(length(input)); - - (void)convert(input, result.data()); - return result; - } - - template< - typename StringType, - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - requires requires(StringType& string) - { - string.resize(std::declval()); - { - string.data() - } -> std::convertible_to::pointer>; - } - [[nodiscard]] constexpr static auto convert(const pointer_type input) noexcept -> StringType - { - StringType result{}; - result.resize(length(input)); - - return convert({input, std::char_traits::length(input)}, result.data()); - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert(const input_type input) noexcept -> std::basic_string::value_type> - { - std::basic_string::value_type> result{}; - result.resize(length(input)); - - (void)convert(input, result.data()); - return result; - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert(const pointer_type input) noexcept -> std::basic_string::value_type> - { - std::basic_string::value_type> result{}; - result.resize(length(input)); - - return convert({input, std::char_traits::length(input)}, result.data()); - } - }; - - template<> - struct simd_processor_of - { - using type = Simd<"icelake.utf32">; - }; - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -} // namespace gal::prometheus::chars diff --git a/src/chars/icelake_utf8.ixx b/src/chars/icelake_utf8.ixx deleted file mode 100644 index b2dc3e88..00000000 --- a/src/chars/icelake_utf8.ixx +++ /dev/null @@ -1,2139 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#if __has_include() -#include -#endif -#if __has_include() -#include -#endif -#include - -export module gal.prometheus.chars:icelake.utf8; - -import std; -import gal.prometheus.meta; -import gal.prometheus.memory; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -import :encoding; -import :scalar.utf8; - -#else -#if __has_include() -#include -#endif -#if __has_include() -#include -#endif -#include - -#include -#include -#include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -#endif - -namespace gal::prometheus::chars -{ - namespace icelake_utf8_detail - { - using data_type = __m512i; - - template - requires - (I0 <= 3) and - (I1 <= 3) and - (I2 <= 3) and - (I3 <= 3) - [[nodiscard]] auto shuffle(const data_type value) noexcept -> data_type - { - constexpr auto s = static_cast(I0 | (I1 << 2) | (I2 << 4) | (I3 << 6)); - return _mm512_shuffle_i32x4(value, value, s); - } - - template - requires(I <= 3) - [[nodiscard]] auto broadcast(const data_type value) noexcept -> data_type // - { - return shuffle(value); - } - - // warning: ignoring attributes on template argument ‘gal::prometheus::chars::icelake_utf8_detail::data_type’ {aka ‘__m512i’} [-Wignored-attributes] - #if defined(GAL_PROMETHEUS_COMPILER_GNU) - [[nodiscard]] inline auto expand_and_identify(const data_type lane_0, const data_type lane_1, int& valid_count) noexcept -> data_type - #else - [[nodiscard]] inline auto expand_and_identify(const data_type lane_0, const data_type lane_1) noexcept -> std::pair - #endif - { - const auto expand_ver2 = _mm512_setr_epi64( - 0x0403'0201'0302'0100, - 0x0605'0403'0504'0302, - 0x0807'0605'0706'0504, - 0x0a09'0807'0908'0706, - 0x0c0b'0a09'0b0a'0908, - 0x0e0d'0c0b'0d0c'0b0a, - 0x000f'0e0d'0f0e'0d0c, - 0x0201'000f'0100'0f0e - ); - const auto v_00c0 = _mm512_set1_epi32(0xc0); - const auto v_0080 = _mm512_set1_epi32(0x80); - - const auto merged = _mm512_mask_mov_epi32(lane_0, 0x1000, lane_1); - const auto input = _mm512_shuffle_epi8(merged, expand_ver2); - const auto t0 = _mm512_and_si512(input, v_00c0); - const auto leading_bytes = _mm512_cmpneq_epu32_mask(t0, v_0080); - - #if defined(GAL_PROMETHEUS_COMPILER_GNU) - valid_count = static_cast(std::popcount(leading_bytes)); - return _mm512_mask_compress_epi32(_mm512_setzero_si512(), leading_bytes, input); - #else - return { - _mm512_mask_compress_epi32(_mm512_setzero_si512(), leading_bytes, input), - static_cast(std::popcount(leading_bytes)) - }; - #endif - } - - [[nodiscard]] inline auto expand_utf8_to_utf32(const data_type input, const data_type char_class) noexcept -> data_type - { - // Input: - // - utf8: bytes stored at separate 32-bit code units - // - valid: which code units have valid UTF-8 characters - // - // Bit layout of single word. We show 4 cases for each possible UTF-8 character encoding. - // The `?` denotes bits we must not assume their value. - // - // |10dd.dddd|10cc.cccc|10bb.bbbb|1111.0aaa| 4-byte char - // |????.????|10cc.cccc|10bb.bbbb|1110.aaaa| 3-byte char - // |????.????|????.????|10bb.bbbb|110a.aaaa| 2-byte char - // |????.????|????.????|????.????|0aaa.aaaa| ASCII char - // byte 3 byte 2 byte 1 byte 0 - // ReSharper disable once CppJoinDeclarationAndAssignment - data_type result; - - // 1. Reset control bits of continuation bytes and the MSB of the leading byte, - // this makes all bytes unsigned (and does not alter ASCII char). - // - // |00dd.dddd|00cc.cccc|00bb.bbbb|0111.0aaa| 4-byte char - // |00??.????|00cc.cccc|00bb.bbbb|0110.aaaa| 3-byte char - // |00??.????|00??.????|00bb.bbbb|010a.aaaa| 2-byte char - // |00??.????|00??.????|00??.????|0aaa.aaaa| ASCII char - // ^^ ^^ ^^ ^ - result = _mm512_and_si512(input, _mm512_set1_epi32(0x3f3f'3f7f)); - - // 2. Swap and join fields A-B and C-D - // - // |0000.cccc|ccdd.dddd|0001.110a|aabb.bbbb| 4-byte char - // |0000.cccc|cc??.????|0001.10aa|aabb.bbbb| 3-byte char - // |0000.????|????.????|0001.0aaa|aabb.bbbb| 2-byte char - // |0000.????|????.????|000a.aaaa|aa??.????| ASCII char - result = _mm512_maddubs_epi16(result, _mm512_set1_epi32(0x0140'0140)); - - // 3. Swap and join fields AB & CD - // - // |0000.0001|110a.aabb|bbbb.cccc|ccdd.dddd| 4-byte char - // |0000.0001|10aa.aabb|bbbb.cccc|cc??.????| 3-byte char - // |0000.0001|0aaa.aabb|bbbb.????|????.????| 2-byte char - // |0000.000a|aaaa.aa??|????.????|????.????| ASCII char - result = _mm512_madd_epi16(result, _mm512_set1_epi32(0x0001'1000)); - - // 4. Shift left the values by variable amounts to reset highest UTF-8 bits - // |aaab.bbbb|bccc.cccd|dddd.d000|0000.0000| 4-byte char -- by 11 - // |aaaa.bbbb|bbcc.cccc|????.??00|0000.0000| 3-byte char -- by 10 - // |aaaa.abbb|bbb?.????|????.???0|0000.0000| 2-byte char -- by 9 - // |aaaa.aaa?|????.????|????.????|?000.0000| ASCII char -- by 7 - // - // continuation = 0 - // ascii = 7 - // 2_bytes = 9 - // 3_bytes = 10 - // 4_bytes = 11 - // - // shift_left_v3 = 4 * [ - // ascii, # 0000 - // ascii, # 0001 - // ascii, # 0010 - // ascii, # 0011 - // ascii, # 0100 - // ascii, # 0101 - // ascii, # 0110 - // ascii, # 0111 - // continuation, # 1000 - // continuation, # 1001 - // continuation, # 1010 - // continuation, # 1011 - // 2_bytes, # 1100 - // 2_bytes, # 1101 - // 3_bytes, # 1110 - // 4_bytes, # 1111 - // ] - result = _mm512_sllv_epi32( - result, - _mm512_shuffle_epi8( - // shift_left_v3 - _mm512_setr_epi64( - 0x0707'0707'0707'0707, - 0x0b0a'0909'0000'0000, - 0x0707'0707'0707'0707, - 0x0b0a'0909'0000'0000, - 0x0707'0707'0707'0707, - 0x0b0a'0909'0000'0000, - 0x0707'0707'0707'0707, - 0x0b0a'0909'0000'0000 - ), - char_class - ) - ); - - // 5. Shift right the values by variable amounts to reset lowest bits - // |0000.0000|000a.aabb|bbbb.cccc|ccdd.dddd| 4-byte char -- by 11 - // |0000.0000|0000.0000|aaaa.bbbb|bbcc.cccc| 3-byte char -- by 16 - // |0000.0000|0000.0000|0000.0aaa|aabb.bbbb| 2-byte char -- by 21 - // |0000.0000|0000.0000|0000.0000|0aaa.aaaa| ASCII char -- by 25 - result = _mm512_srlv_epi32( - result, - _mm512_shuffle_epi8( - // shift_right - // 4 * [25, 25, 25, 25, 25, 25, 25, 25, 0, 0, 0, 0, 21, 21, 16, 11] - _mm512_setr_epi64( - 0x1919'1919'1919'1919, - 0x0b10'1515'0000'0000, - 0x1919'1919'1919'1919, - 0x0b10'1515'0000'0000, - 0x1919'1919'1919'1919, - 0x0b10'1515'0000'0000, - 0x1919'1919'1919'1919, - 0x0b10'1515'0000'0000), - char_class - ) - ); - - return result; - } - - [[nodiscard]] inline auto expand_utf8_to_utf32(const data_type input) noexcept -> data_type - { - const auto v_0000_000f = _mm512_set1_epi32(0x0000'000f); - const auto v_8080_8000 = _mm512_set1_epi32(static_cast(0x8080'8000)); - - const auto char_class = _mm512_ternarylogic_epi32(_mm512_srli_epi32(input, 4), v_0000_000f, v_8080_8000, 0xea); - return expand_utf8_to_utf32(input, char_class); - } - - template - [[nodiscard]] auto store_ascii(const data_type in, const data_type byte_flip, auto it_output_current) noexcept -> std::size_t - { - if constexpr (Category == CharsCategory::UTF32) - { - const auto t0 = _mm512_castsi512_si128(in); - const auto t1 = _mm512_extracti32x4_epi32(in, 1); - const auto t2 = _mm512_extracti32x4_epi32(in, 2); - const auto t3 = _mm512_extracti32x4_epi32(in, 3); - - _mm512_storeu_si512(it_output_current + 0 * 16, _mm512_cvtepu8_epi32(t0)); - _mm512_storeu_si512(it_output_current + 1 * 16, _mm512_cvtepu8_epi32(t1)); - _mm512_storeu_si512(it_output_current + 2 * 16, _mm512_cvtepu8_epi32(t2)); - _mm512_storeu_si512(it_output_current + 3 * 16, _mm512_cvtepu8_epi32(t3)); - } - else if constexpr (Category == CharsCategory::UTF16_LE or Category == CharsCategory::UTF16_BE) - { - const auto h0 = _mm512_castsi512_si256(in); - const auto h1 = _mm512_extracti64x4_epi64(in, 1); - if constexpr (Category == CharsCategory::UTF16_BE) - { - _mm512_storeu_si512(it_output_current + 0 * 16, _mm512_shuffle_epi8(_mm512_cvtepu8_epi16(h0), byte_flip)); - _mm512_storeu_si512(it_output_current + 2 * 16, _mm512_shuffle_epi8(_mm512_cvtepu8_epi16(h1), byte_flip)); - } - else - { - _mm512_storeu_si512(it_output_current + 0 * 16, _mm512_cvtepu8_epi16(h0)); - _mm512_storeu_si512(it_output_current + 2 * 16, _mm512_cvtepu8_epi16(h1)); - } - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - - return 64; - } - - // utf32_to_utf16 converts `count` lower UTF-32 code units from input `utf32` into UTF-16. It may overflow. - // return how many 16-bit code units were stored. - template - [[nodiscard]] auto utf32_to_utf16(const data_type input, const data_type byte_flip, const int count, auto it_output_current) noexcept -> std::size_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(count > 0); - - const auto v_0000_ffff = _mm512_set1_epi32(0x0000'ffff); - const auto v_0001_0000 = _mm512_set1_epi32(0x0001'0000); - const auto v_ffff_0000 = _mm512_set1_epi32(static_cast(0xffff'0000)); - const auto v_fc00_fc00 = _mm512_set1_epi32(static_cast(0xfc00'fc00)); - const auto v_d800_dc00 = _mm512_set1_epi32(static_cast(0xd800'dc00)); - - // used only if Masked - const auto count_mask = static_cast<__mmask16>((1 << count) - 1); - - // check if we have any surrogate pairs - const auto surrogate_pair_mask = [input, v_0000_ffff, count_mask] - { - if constexpr (Masked) { return _mm512_mask_cmpgt_epu32_mask(count_mask, input, v_0000_ffff); } - else - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'count_mask' is not used [-Werror,-Wunused-lambda-capture] - (void)count_mask; - #endif - - return _mm512_cmpgt_epu32_mask(input, v_0000_ffff); - } - }(); - if (surrogate_pair_mask == 0) - { - if constexpr (IsBigEndian) - { - if constexpr (Masked) - { - _mm256_mask_storeu_epi16( - it_output_current, - count_mask, - _mm256_shuffle_epi8(_mm512_cvtepi32_epi16(input), _mm512_castsi512_si256(byte_flip)) - ); - } - else - { - _mm256_storeu_si256( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m256i*, it_output_current), - _mm256_shuffle_epi8( - _mm512_cvtepi32_epi16(input), - _mm512_castsi512_si256(byte_flip) - ) - ); - } - } - else - { - if constexpr (Masked) { _mm256_mask_storeu_epi16(it_output_current, count_mask, _mm512_cvtepi32_epi16(input)); } - else - { - _mm256_storeu_si256( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(__m256i*, it_output_current), - _mm512_cvtepi32_epi16(input) - ); - } - } - - return count; - } - - const auto total_count = count + std::popcount(surrogate_pair_mask); - - // build surrogate pair code units in 32-bit lanes - - // t0 = 8 x [000000000000aaaa|aaaaaabbbbbbbbbb] - const auto t0 = _mm512_sub_epi32(input, v_0001_0000); - // t1 = 8 x [000000aaaaaaaaaa|bbbbbbbbbb000000] - const auto t1 = _mm512_slli_epi32(t0, 6); - // t2 = 8 x [000000aaaaaaaaaa|aaaaaabbbbbbbbbb] -- copy hi word from t1 to t0 - // 0xe4 = (t1 and v_ffff_0000) or (t0 and not v_ffff_0000) - const auto t2 = _mm512_ternarylogic_epi32(t1, t0, v_ffff_0000, 0xe4); - // t2 = 8 x [110110aaaaaaaaaa|110111bbbbbbbbbb] -- copy hi word from t1 to t0 - // 0xba = (t2 and not v_fc00_fc000) or v_d800_dc00 - const auto t3 = _mm512_ternarylogic_epi32(t2, v_fc00_fc00, v_d800_dc00, 0xba); - const auto t4 = _mm512_mask_blend_epi32(surrogate_pair_mask, input, t3); - const auto t5 = [t4, byte_flip] - { - if constexpr (IsBigEndian) { return _mm512_shuffle_epi8(_mm512_ror_epi32(t4, 16), byte_flip); } - else - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'byte_flip' is not used [-Werror,-Wunused-lambda-capture] - (void)byte_flip; - #endif - - return _mm512_ror_epi32(t4, 16); - } - }(); - - const auto non_zero = _kor_mask32(0xaaaa'aaaa, _mm512_cmpneq_epi16_mask(t5, _mm512_setzero_si512())); - // _mm512_mask_compressstoreu_epi16(it_output_current, non_zero, t5); - _mm512_mask_storeu_epi16( - it_output_current, - static_cast<__mmask32>((1 << total_count) - 1), - _mm512_maskz_compress_epi16(non_zero, t5) - ); - - return total_count; - } - - template - [[nodiscard]] auto store_utf16_or_utf32(const data_type in, const data_type byte_flip, const int count, auto it_output_current) noexcept -> std::size_t - { - if constexpr (Category == CharsCategory::UTF32) - { - if constexpr (Masked) - { - const auto mask = static_cast<__mmask16>((1 << count) - 1); - _mm512_mask_storeu_epi32(it_output_current, mask, in); - } - else { _mm512_storeu_si512(it_output_current, in); } - - return count; - } - else if constexpr (Category == CharsCategory::UTF16_LE or Category == CharsCategory::UTF16_BE) - { - return icelake_utf8_detail::utf32_to_utf16( - in, - byte_flip, - count, - it_output_current - ); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template - [[nodiscard]] auto transcode_16(const data_type lane_0, const data_type lane_1, const data_type byte_flip, auto it_output_current) noexcept -> std::size_t - { - // # lane{0,1,2} have got bytes: - // [ 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15] - // # lane3 has got bytes: - // [ 16, 17, 18, 19, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15] - // - // expand_ver2 = [ - // # lane 0: - // 0, 1, 2, 3, - // 1, 2, 3, 4, - // 2, 3, 4, 5, - // 3, 4, 5, 6, - // - // # lane 1: - // 4, 5, 6, 7, - // 5, 6, 7, 8, - // 6, 7, 8, 9, - // 7, 8, 9, 10, - // - // # lane 2: - // 8, 9, 10, 11, - // 9, 10, 11, 12, - // 10, 11, 12, 13, - // 11, 12, 13, 14, - // - // # lane 3 order: 13, 14, 15, 16 14, 15, 16, 17, 15, 16, 17, 18, 16, 17, 18, 19 - // 12, 13, 14, 15, - // 13, 14, 15, 0, - // 14, 15, 0, 1, - // 15, 0, 1, 2, - // ] - const auto expand_ver2 = _mm512_setr_epi64( - 0x0403'0201'0302'0100, - 0x0605'0403'0504'0302, - 0x0807'0605'0706'0504, - 0x0a09'0807'0908'0706, - 0x0c0b'0a09'0b0a'0908, - 0x0e0d'0c0b'0d0c'0b0a, - 0x000f'0e0d'0f0e'0d0c, - 0x0201'000f'0100'0f0e - ); - const auto v_0000_00c0 = _mm512_set1_epi32(0x0000'00c0); - const auto v_0000_0080 = _mm512_set1_epi32(0x0000'0080); - - const auto merged = _mm512_mask_mov_epi32(lane_0, 0x1000, lane_1); - const auto input = _mm512_shuffle_epi8(merged, expand_ver2); - - const auto t0 = _mm512_and_si512(input, v_0000_00c0); - const auto leading_bytes = _mm512_cmpneq_epu32_mask(t0, v_0000_0080); - const auto utf32 = expand_utf8_to_utf32(input); - const auto out = _mm512_mask_compress_epi32(_mm512_setzero_si512(), leading_bytes, utf32); - const auto valid_count = std::popcount(leading_bytes); - - if constexpr (Category == CharsCategory::UTF32) - { - if constexpr (Masked) { _mm512_mask_storeu_epi32(it_output_current, static_cast<__mmask16>((1 << valid_count) - 1), out); } - else { _mm512_storeu_si512(it_output_current, out); } - - return valid_count; - } - else if constexpr (Category == CharsCategory::UTF16_LE or Category == CharsCategory::UTF16_BE) - { - return utf32_to_utf16(out, byte_flip, valid_count, it_output_current); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - struct avx512_utf8_checker - { - private: - template - requires(N <= 32) - static data_type prev(const data_type input, const data_type prev_input) noexcept - { - const auto move_mask = _mm512_setr_epi32(28, 29, 30, 31, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); - const auto rotated = _mm512_permutex2var_epi32(input, move_mask, prev_input); - - return _mm512_alignr_epi8(input, rotated, 16 - N); - } - - public: - // If this is nonzero, there has been a UTF-8 error. - data_type error{}; - // The last input we received - data_type prev_input_block{}; - // Whether the last input we received was incomplete (used for ASCII fast path) - data_type prev_incomplete{}; - - private: - // Check whether the current bytes are valid UTF-8. - void check_utf8_bytes(const data_type input, const data_type prev_input) noexcept - { - // Flip prev1...prev3, so we can easily determine if they are 2+, 3+ or 4+ lead bytes - // (2, 3, 4-byte leads become large positive numbers instead of small negative numbers) - const auto prev_1 = avx512_utf8_checker::prev<1>(input, prev_input); - - // special cases - const auto source = [input, prev_1]() noexcept -> data_type - { - const auto mask1 = _mm512_setr_epi64( - 0x0202'0202'0202'0202, - 0x4915'0121'8080'8080, - 0x0202'0202'0202'0202, - 0x4915'0121'8080'8080, - 0x0202'0202'0202'0202, - 0x4915'0121'8080'8080, - 0x0202'0202'0202'0202, - 0x4915'0121'8080'8080); - const auto mask2 = _mm512_setr_epi64( - static_cast(0xcbcb'cb8b'8383'a3e7), - static_cast(0xcbcb'dbcb'cbcb'cbcb), - static_cast(0xcbcb'cb8b'8383'a3e7), - static_cast(0xcbcb'dbcb'cbcb'cbcb), - static_cast(0xcbcb'cb8b'8383'a3e7), - static_cast(0xcbcb'dbcb'cbcb'cbcb), - static_cast(0xcbcb'cb8b'8383'a3e7), - static_cast(0xcbcb'dbcb'cbcb'cbcb)); - const auto mask3 = _mm512_setr_epi64( - 0x0101'0101'0101'0101, - 0x0101'0101'baba'aee6, - 0x0101'0101'0101'0101, - 0x0101'0101'baba'aee6, - 0x0101'0101'0101'0101, - 0x0101'0101'baba'aee6, - 0x0101'0101'0101'0101, - 0x0101'0101'baba'aee6); - - const auto v_0f = _mm512_set1_epi8(static_cast(0x0f)); - - const auto index1 = _mm512_and_si512(_mm512_srli_epi16(prev_1, 4), v_0f); - const auto index2 = _mm512_and_si512(prev_1, v_0f); - const auto index3 = _mm512_and_si512(_mm512_srli_epi16(input, 4), v_0f); - - const auto byte_1_high = _mm512_shuffle_epi8(mask1, index1); - const auto byte_1_low = _mm512_shuffle_epi8(mask2, index2); - const auto byte_2_high = _mm512_shuffle_epi8(mask3, index3); - - return _mm512_ternarylogic_epi64(byte_1_high, byte_1_low, byte_2_high, 128); - }(); - - // multibyte length - const auto length = [input, prev_input, source]() noexcept -> data_type - { - const auto v_7f = _mm512_set1_epi8(static_cast(0x7f)); - const auto v_80 = _mm512_set1_epi8(static_cast(0x80)); - - const auto prev_2 = avx512_utf8_checker::prev<2>(input, prev_input); - const auto prev_3 = avx512_utf8_checker::prev<3>(input, prev_input); - - // Only 111????? will be > 0 - const auto third = _mm512_subs_epu8(prev_2, _mm512_set1_epi8(static_cast(0b1110'0000 - 1))); - // Only 1111???? will be > 0 - const auto fourth = _mm512_subs_epu8(prev_3, _mm512_set1_epi8(static_cast(0b1111'0000 - 1))); - const auto third_or_fourth = _mm512_or_si512(third, fourth); - - return _mm512_ternarylogic_epi32(_mm512_adds_epu8(v_7f, third_or_fourth), v_80, source, 0b110'1010); - }(); - - error = _mm512_or_si512(length, error); - } - - // Return nonzero if there are incomplete multibyte characters at the end of the block: - // e.g. if there is a 4-byte character, but it's 3 bytes from the end. - void check_incomplete(const data_type input) noexcept - { - // If the previous input's last 3 bytes match this, they're too short (they ended at EOF): - // ... 1111???? 111????? 11?????? - const auto max_value = _mm512_setr_epi64( - static_cast(0xffff'ffff'ffff'ffff), - static_cast(0xffff'ffff'ffff'ffff), - static_cast(0xffff'ffff'ffff'ffff), - static_cast(0xffff'ffff'ffff'ffff), - static_cast(0xffff'ffff'ffff'ffff), - static_cast(0xffff'ffff'ffff'ffff), - static_cast(0xffff'ffff'ffff'ffff), - static_cast(0xbfdf'efff'ffff'ffff)); - prev_incomplete = _mm512_subs_epu8(input, max_value); - } - - public: - void check_eof() noexcept - { - // The only problem that can happen at EOF is that a multi-byte character is too short - // or a byte value too large in the last bytes: check_utf8_bytes::check_special_cases only - // checks for bytes too large in the first of two bytes. - - // If the previous block had incomplete UTF-8 characters at the end, an ASCII block can't - // possibly finish them. - error = _mm512_or_si512(error, prev_incomplete); - } - - [[nodiscard]] bool has_error() const noexcept { return _mm512_test_epi8_mask(error, error) != 0; } - - // returns true if ASCII. - bool check_input(const data_type input) noexcept - { - const auto v_80 = _mm512_set1_epi8(static_cast(0x80)); - - if (const auto ascii = _mm512_test_epi8_mask(input, v_80); - ascii == 0) - { - check_eof(); - return true; - } - - check_utf8_bytes(input, prev_input_block); - check_incomplete(input); - prev_input_block = input; - return false; - } - }; - - template - class SimdUtf8Base - { - public: - using scalar_type = std::conditional_t, Scalar<"utf8_char">>; - - constexpr static auto input_category = scalar_type::input_category; - using input_type = typename scalar_type::input_type; - using char_type = typename scalar_type::char_type; - using pointer_type = typename scalar_type::pointer_type; - using size_type = typename scalar_type::size_type; - - template - [[nodiscard]] constexpr static auto validate(const input_type input) noexcept -> std::conditional_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - avx512_utf8_checker checker{}; - - std::size_t count = 0; - for (; it_input_current + 64 <= it_input_end; it_input_current += 64, count += 64) - { - const auto in = _mm512_loadu_si512(it_input_current); - checker.check_input(in); - - if constexpr (ReturnResultType) - { - if (checker.has_error()) - { - if (count != 0) - { - // Sometimes the error is only detected in the next chunk - count -= 1; - } - - auto result = scalar_type::rewind_and_validate(it_input_begin, it_input_begin + count, it_input_end); - result.count += count; - return result; - } - } - } - - const auto in = _mm512_maskz_loadu_epi8((__mmask64{1} << (it_input_end - it_input_current)) - 1, it_input_current); - checker.check_input(in); - - if constexpr (ReturnResultType) - { - if (checker.has_error()) - { - if (count != 0) - { - // Sometimes the error is only detected in the next chunk - count -= 1; - } - - auto result = scalar_type::rewind_and_validate(it_input_begin, it_input_begin + count, it_input_end); - result.count += count; - return result; - } - } - else { checker.check_eof(); } - - if constexpr (ReturnResultType) { return result_type{.error = ErrorCode::NONE, .count = input_length}; } - else { return not checker.has_error(); } - } - - template - [[nodiscard]] constexpr static auto validate(const pointer_type input) noexcept -> std::conditional_t - { - return validate({input, std::char_traits::length(input)}); - } - - // note: we are not BOM aware - template - [[nodiscard]] constexpr static auto length(const input_type input) noexcept -> size_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - if constexpr (OutputCategory == CharsCategory::ASCII) - { - const auto continuation = _mm512_set1_epi8(static_cast(0b1011'1111)); - - __m512i unrolled_length = _mm512_setzero_si512(); - int reparation_length = 0; - - while (it_input_current + sizeof(__m512i) <= it_input_end) - { - const auto iterations = static_cast((it_input_end - it_input_current) / sizeof(__m512i)); - const auto this_turn_end = it_input_current + iterations * sizeof(__m512i) - sizeof(__m512i); - - for (; it_input_current + 8 * sizeof(__m512i) <= this_turn_end; it_input_current += 8 * sizeof(__m512i)) - { - const auto in_0 = _mm512_loadu_si512( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m512i *, it_input_current + 0 * sizeof(__m512i))); - const auto in_1 = _mm512_loadu_si512( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m512i *, it_input_current + 1 * sizeof(__m512i))); - const auto in_2 = _mm512_loadu_si512( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m512i *, it_input_current + 2 * sizeof(__m512i))); - const auto in_3 = _mm512_loadu_si512( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m512i *, it_input_current + 3 * sizeof(__m512i))); - const auto in_4 = _mm512_loadu_si512( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m512i *, it_input_current + 4 * sizeof(__m512i))); - const auto in_5 = _mm512_loadu_si512( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m512i *, it_input_current + 5 * sizeof(__m512i))); - const auto in_6 = _mm512_loadu_si512( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m512i *, it_input_current + 6 * sizeof(__m512i))); - const auto in_7 = _mm512_loadu_si512( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m512i *, it_input_current + 7 * sizeof(__m512i))); - - const auto mask_0 = _mm512_cmple_epi8_mask(in_0, continuation); - const auto mask_1 = _mm512_cmple_epi8_mask(in_1, continuation); - const auto mask_2 = _mm512_cmple_epi8_mask(in_2, continuation); - const auto mask_3 = _mm512_cmple_epi8_mask(in_3, continuation); - const auto mask_4 = _mm512_cmple_epi8_mask(in_4, continuation); - const auto mask_5 = _mm512_cmple_epi8_mask(in_5, continuation); - const auto mask_6 = _mm512_cmple_epi8_mask(in_6, continuation); - const auto mask_7 = _mm512_cmple_epi8_mask(in_7, continuation); - - const auto mask_register = _mm512_set_epi64(mask_7, mask_6, mask_5, mask_4, mask_3, mask_2, mask_1, mask_0); - - unrolled_length = _mm512_add_epi64(unrolled_length, _mm512_popcnt_epi64(mask_register)); - } - - for (; it_input_current <= this_turn_end; it_input_current += sizeof(__m512i)) - { - const auto in = _mm512_loadu_si512( - GAL_PROMETHEUS_SEMANTIC_TRIVIAL_REINTERPRET_CAST(const __m512i*, it_input_current + 0 * sizeof(__m512i)) - ); - const auto continuation_bitmask = _mm512_cmple_epi8_mask(in, continuation); - reparation_length += std::popcount(continuation_bitmask); - } - } - - const auto half_0 = _mm512_extracti64x4_epi64(unrolled_length, 0); - const auto half_1 = _mm512_extracti64x4_epi64(unrolled_length, 1); - const auto result_length = - // number of 512-bit chunks that fits into the length. - input_length / sizeof(__m512i) * sizeof(__m512i) + // - -reparation_length + - -_mm256_extract_epi64(half_0, 0) + - -_mm256_extract_epi64(half_0, 1) + - -_mm256_extract_epi64(half_0, 2) + - -_mm256_extract_epi64(half_0, 3) + - -_mm256_extract_epi64(half_1, 0) + - -_mm256_extract_epi64(half_1, 1) + - -_mm256_extract_epi64(half_1, 2) + - -_mm256_extract_epi64(half_1, 3); - - return result_length + scalar_type::code_points({it_input_current, static_cast(it_input_end - it_input_current)}); - } - else if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) { return input.size(); } // NOLINT(bugprone-branch-clone) - else if constexpr (OutputCategory == CharsCategory::UTF16_LE or OutputCategory == CharsCategory::UTF16_BE or OutputCategory == CharsCategory::UTF16) - { - auto result_length = static_cast(0); - - for (; it_input_current + 64 <= it_input_end; it_input_current += 64) - { - // @see scalar_type::code_points - // @see scalar_type::length - - const auto in = _mm512_loadu_si512(it_input_current); - - const auto utf8_continuation_mask = _mm512_cmple_epi8_mask(in, _mm512_set1_epi8(-65 + 1)); - // We count one word for anything that is not a continuation (so leading bytes). - result_length += 64 - std::popcount(utf8_continuation_mask); - - const auto utf8_4_byte = _mm512_cmpge_epu8_mask(in, _mm512_set1_epi8(static_cast(240))); - result_length += std::popcount(utf8_4_byte); - } - - return result_length + scalar_type::template length({it_input_current, static_cast(it_input_end - it_input_current)}); - } - else if constexpr (OutputCategory == CharsCategory::UTF32) { return length(input); } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - // note: we are not BOM aware - template - [[nodiscard]] constexpr static auto length(const pointer_type input) noexcept -> size_type - { - return length({input, std::char_traits::length(input)}); - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert( - const input_type input, - typename output_type::pointer output - ) noexcept -> std::conditional_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); - if constexpr (ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(validate(input)); - } - - using output_pointer_type = typename output_type::pointer; - // using output_char_type = typename output_type::value_type; - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - const output_pointer_type it_output_begin = output; - output_pointer_type it_output_current = it_output_begin; - - if constexpr (OutputCategory == CharsCategory::ASCII) - { - const auto process = - [&it_output_current]( - const input_type process_input, - __mmask64& out_next_leading, - __mmask64& out_next_bit6 - ) noexcept -> bool - { - // 11111111111 ... 1100 0000 - const auto minus64 = _mm512_set1_epi8(-64); - const auto one = _mm512_set1_epi8(1); - - const auto input_process_length = process_input.size(); - auto it_input_process_current = process_input.data(); - - const auto load_mask = [](const auto length) - { - if constexpr (MaskOut) { return _bzhi_u64(~0ull, static_cast(length)); } - else { return 0xffff'ffff'ffff'ffff; } - }(input_process_length); - - const auto in = _mm512_maskz_loadu_epi8(load_mask, it_input_process_current); - const auto non_ascii = _mm512_movepi8_mask(in); - - if (non_ascii == 0) - { - if constexpr (MaskOut) { _mm512_mask_storeu_epi8(it_output_current, load_mask, in); } - else { _mm512_storeu_si512(it_output_current, in); } - - it_output_current += input_process_length; - return true; - } - - const auto leading = _mm512_cmpge_epu8_mask(in, minus64); - const auto high_bits = _mm512_xor_si512(in, _mm512_set1_epi8(-62)); - - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if (const auto invalid_leading_bytes = _mm512_mask_cmpgt_epu8_mask(leading, high_bits, one); - invalid_leading_bytes) { return false; } - - if (const auto leading_shift = (leading << 1) | out_next_leading; - (non_ascii ^ leading) != leading_shift) { return false; } - } - - const auto bit6 = _mm512_cmpeq_epi8_mask(high_bits, one); - const auto retain = ~leading & load_mask; - const auto written_out = std::popcount(retain); - const auto store_mask = (__mmask64{1} << written_out) - 1; - - const auto out = _mm512_maskz_compress_epi8( - retain, - _mm512_mask_sub_epi8(in, (bit6 << 1) | out_next_bit6, in, minus64)); - - _mm512_mask_storeu_epi8(it_output_current, store_mask, out); - - it_output_current += written_out; - out_next_leading = leading >> 63; - out_next_bit6 = bit6 >> 63; - - return true; - }; - - const auto fallback = [&it_output_current, it_output_begin](const input_type fallback_input) noexcept -> auto - { - const auto result = scalar_type::template convert( - fallback_input, - it_output_current - ); - if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{result.error, result.count + static_cast(it_output_current - it_output_begin)}; - } - else - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'it_output_begin' is not used [-Werror,-Wunused-lambda-capture] - (void)it_output_begin; - #endif - - return result; - } - }; - - __mmask64 next_leading = 0; - __mmask64 next_bit6 = 0; - - for (; it_input_current + 64 <= it_input_end; it_input_current += 64) - { - const auto process_result = process.template operator()({it_input_current, 64}, next_leading, next_bit6); - - if constexpr (ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT) { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(process_result == true); } - else { if (not process_result) { return fallback({it_input_current, static_cast(it_input_end - it_input_current)}); } } - } - - if (const auto remaining = static_cast(it_input_end - it_input_current); - remaining != 0) - { - const auto process_result = process.template operator()({it_input_current, remaining}, next_leading, next_bit6); - - if constexpr (ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT) { GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(process_result == true); } - else { if (not process_result) { return fallback({it_input_current, remaining}); } } - } - } - else if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) - { - std::memcpy(it_output_current, it_input_current, input_length * sizeof(char_type)); - it_input_current += input_length; - it_output_current += input_length; - } - else if constexpr ( - OutputCategory == CharsCategory::UTF16_LE or - OutputCategory == CharsCategory::UTF16_BE or - OutputCategory == CharsCategory::UTF32 - ) - { - const auto byte_flip = _mm512_setr_epi64( - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809, - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809, - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809, - 0x0607'0405'0203'0001, - 0x0e0f'0c0d'0a0b'0809); - - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if constexpr (OutputCategory == CharsCategory::UTF16_LE or OutputCategory == CharsCategory::UTF16_BE) - { - constexpr auto is_big_endian = OutputCategory == CharsCategory::UTF16_BE; - - const auto process = [&it_input_current, &it_output_current, byte_flip](const auto remaining) noexcept -> bool - { - // clang-format off - const auto mask_identity = _mm512_set_epi8( - 63, - 62, - 61, - 60, - 59, - 58, - 57, - 56, - 55, - 54, - 53, - 52, - 51, - 50, - 49, - 48, - 47, - 46, - 45, - 44, - 43, - 42, - 41, - 40, - 39, - 38, - 37, - 36, - 35, - 34, - 33, - 32, - 31, - 30, - 29, - 28, - 27, - 26, - 25, - 24, - 23, - 22, - 21, - 20, - 19, - 18, - 17, - 16, - 15, - 14, - 13, - 12, - 11, - 10, - 9, - 8, - 7, - 6, - 5, - 4, - 3, - 2, - 1, - 0 - ); - // clang-format on - const auto v_c0c0_c0c0 = _mm512_set1_epi32(static_cast(0xc0c0'c0c0)); - const auto v_0400_0400 = _mm512_set1_epi32(0x0400'0400); - const auto v_8080_8080 = _mm512_set1_epi32(static_cast(0x8080'8080)); - const auto v_0800_0800 = _mm512_set1_epi32(0x0800'0800); - const auto v_d800_d800 = _mm512_set1_epi32(static_cast(0xd800'd800)); - const auto v_f0f0_f0f0 = _mm512_set1_epi32(static_cast(0xf0f0'f0f0)); - const auto v_dfdf_dfdf = _mm512_set_epi64( - static_cast(0xffff'dfdf'dfdf'dfdf), - static_cast(0xdfdf'dfdf'dfdf'dfdf), - static_cast(0xdfdf'dfdf'dfdf'dfdf), - static_cast(0xdfdf'dfdf'dfdf'dfdf), - static_cast(0xdfdf'dfdf'dfdf'dfdf), - static_cast(0xdfdf'dfdf'dfdf'dfdf), - static_cast(0xdfdf'dfdf'dfdf'dfdf), - static_cast(0xdfdf'dfdf'dfdf'dfdf) - ); - const auto v_c2c2_c2c2 = _mm512_set1_epi32(static_cast(0xc2c2'c2c2)); - const auto v_ffff_ffff = _mm512_set1_epi32(static_cast(0xffff'ffff)); - const auto v_d7c0_d7c0 = _mm512_set1_epi32(static_cast(0xd7c0'd7c0)); - const auto v_dc00_dc00 = _mm512_set1_epi32(static_cast(0xdc00'dc00)); - - const auto remaining_mask = [](const auto length) noexcept -> auto - { - if constexpr (MaskOut) { return _bzhi_u64(~0ull, static_cast(length)); } - else { return 0xffff'ffff'ffff'ffff; } - }(remaining); - - const auto in = _mm512_maskz_loadu_epi8(remaining_mask, it_input_current); - const auto mask_byte_1 = _mm512_mask_cmplt_epu8_mask(remaining_mask, in, v_8080_8080); - - // NOT(mask_byte_1) AND valid_input_length -- if all zeroes, then all ASCII - if (_ktestc_mask64_u8(mask_byte_1, remaining_mask)) - { - if constexpr (MaskOut) - { - it_input_current += remaining; - - const auto in_1 = [in, byte_flip]() noexcept -> auto - { - if constexpr (is_big_endian) - { - return _mm512_shuffle_epi8(_mm512_cvtepu8_epi16(_mm512_castsi512_si256(in)), byte_flip); - } - else - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'byte_flip' is not used [-Werror,-Wunused-lambda-capture] - (void)byte_flip; - #endif - - return _mm512_cvtepu8_epi16(_mm512_castsi512_si256(in)); - } - }(); - - if (remaining <= 32) - { - _mm512_mask_storeu_epi16(it_output_current, (__mmask32{1} << remaining) - 1, in_1); - it_output_current += remaining; - } - else - { - const auto in_2 = [in, byte_flip]() noexcept -> auto - { - if constexpr (is_big_endian) - { - return _mm512_shuffle_epi8(_mm512_cvtepu8_epi16(_mm512_extracti64x4_epi64(in, 1)), byte_flip); - } - else - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'byte_flip' is not used [-Werror,-Wunused-lambda-capture] - (void)byte_flip; - #endif - - return _mm512_cvtepu8_epi16(_mm512_extracti64x4_epi64(in, 1)); - } - }(); - - _mm512_storeu_si512(it_output_current, in_1); - it_output_current += 32; - _mm512_mask_storeu_epi16(it_output_current, (__mmask32{1} << (remaining - 32)) - 1, in_2); - it_output_current += remaining - 32; - } - } - else - { - // we convert a full 64-byte block, writing 128 bytes. - it_input_current += 64; - - const auto in_1 = [in, byte_flip]() noexcept -> auto - { - if constexpr (is_big_endian) - { - return _mm512_shuffle_epi8(_mm512_cvtepu8_epi16(_mm512_castsi512_si256(in)), byte_flip); - } - else - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'byte_flip' is not used [-Werror,-Wunused-lambda-capture] - (void)byte_flip; - #endif - - return _mm512_cvtepu8_epi16(_mm512_castsi512_si256(in)); - } - }(); - const auto in_2 = [in, byte_flip]() noexcept -> auto - { - if constexpr (is_big_endian) - { - return _mm512_shuffle_epi8(_mm512_cvtepu8_epi16(_mm512_extracti64x4_epi64(in, 1)), byte_flip); - } - else - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'byte_flip' is not used [-Werror,-Wunused-lambda-capture] - (void)byte_flip; - #endif - - return _mm512_cvtepu8_epi16(_mm512_extracti64x4_epi64(in, 1)); - } - }(); - - _mm512_storeu_si512(it_output_current, in_1); - it_output_current += 32; - _mm512_storeu_si512(it_output_current, in_2); - it_output_current += 32; - } - - return true; - } - - // classify characters further - - // 0xc0 <= in, 2, 3, or 4 leading byte - const auto mask_byte_234 = _mm512_cmp_epu8_mask(v_c0c0_c0c0, in, _MM_CMPINT_LE); - // 0xdf < in, 3 or 4 leading byte - const auto mask_byte_34 = _mm512_cmp_epu8_mask(v_dfdf_dfdf, in, _MM_CMPINT_LT); - - // 0xc0 <= in < 0xc2 (illegal two byte sequence) - if (const auto two_bytes = _mm512_mask_cmp_epu8_mask(mask_byte_234, in, v_c2c2_c2c2, _MM_CMPINT_LT); - _ktestz_mask64_u8(two_bytes, two_bytes) == 0) - { - // Overlong 2-byte sequence - return false; - } - - if (_ktestz_mask64_u8(mask_byte_34, mask_byte_34) == 0) - { - // We have a 3-byte sequence and/or a 2-byte sequence, or possibly even a 4-byte sequence - - // 0xf0 <= zmm0 (4 byte start bytes) - const auto mask_byte_4 = _mm512_cmp_epu8_mask(in, v_f0f0_f0f0, _MM_CMPINT_NLT); - const auto mask_not_ascii = [remaining_mask, mask_byte_1]() noexcept -> auto - { - if constexpr (MaskOut) { return _kand_mask64(_knot_mask64(mask_byte_1), remaining_mask); } - else - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'remaining_mask' is not used [-Werror,-Wunused-lambda-capture] - (void)remaining_mask; - #endif - - return _knot_mask64(mask_byte_1); - } - }(); - - const auto mask_pattern_1 = _kshiftli_mask64(mask_byte_234, 1); - const auto mask_patten_2 = _kshiftli_mask64(mask_byte_34, 2); - if (mask_byte_4 == 0) - { - // expected continuation bytes - const auto mask_combing = _kor_mask64(mask_pattern_1, mask_patten_2); - const auto mask_byte_1234 = _kor_mask64(mask_byte_1, mask_byte_234); - - // mismatched continuation bytes - if constexpr (MaskOut) { if (mask_combing != _kxor_mask64(remaining_mask, mask_byte_1234)) { return false; } } - else - { - // XNOR of mask_combing and mask_byte_1234 should be all zero if they differ the presence of a 1 bit indicates that they overlap. - if (const auto v = _kxnor_mask64(mask_combing, mask_byte_1234); not _kortestz_mask64_u8(v, v)) - { - return false; - } - } - - // identifying the last bytes of each sequence to be decoded - const auto mend = [mask_byte_1234, remaining]() noexcept -> auto - { - if constexpr (MaskOut) { return _kor_mask64(_kshiftri_mask64(mask_byte_1234, 1), __mmask64{1} << (remaining - 1)); } - else - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'remaining' is not used [-Werror,-Wunused-lambda-capture] - (void)remaining; - #endif - - return _kshiftri_mask64(mask_byte_1234, 1); - } - }(); - - const auto last_and_third = _mm512_maskz_compress_epi8(mend, mask_identity); - const auto last_and_third_u16 = _mm512_cvtepu8_epi16(_mm512_castsi512_si256(last_and_third)); - // ASCII: 00000000 other: 11000000 - const auto non_ascii_tags = _mm512_maskz_mov_epi8(mask_not_ascii, v_c0c0_c0c0); - // high two bits cleared where not ASCII - const auto cleared_bytes = _mm512_andnot_si512(non_ascii_tags, in); - // bytes that precede non-ASCII bytes - const auto mask_before_non_ascii = _kshiftri_mask64(mask_not_ascii, 1); - const auto before_ascii_bytes = _mm512_maskz_mov_epi8(mask_before_non_ascii, cleared_bytes); - // the last byte of each character - const auto last_bytes = _mm512_maskz_permutexvar_epi8(0x5555'5555'5555'5555, last_and_third_u16, cleared_bytes); - - // indices of the second last bytes - const auto index_of_second_last_bytes = _mm512_add_epi16(v_ffff_ffff, last_and_third_u16); - // shifted into position - const auto second_last_bytes = _mm512_slli_epi16( - // the second last bytes (of two, three byte seq, surrogates) - _mm512_maskz_permutexvar_epi8(0x5555'5555'5555'5555, index_of_second_last_bytes, before_ascii_bytes), - 6); - - // indices of the third last bytes - const auto index_of_third_last_bytes = _mm512_add_epi16(v_ffff_ffff, index_of_second_last_bytes); - // shifted into position - const auto third_last_bytes = _mm512_slli_epi16( - // the third last bytes (of three byte sequences, high surrogate) - _mm512_maskz_permutexvar_epi8(0x5555'5555'5555'5555, - index_of_third_last_bytes, - // only those that are the third last byte of a sequence - _mm512_maskz_mov_epi8(mask_byte_34, cleared_bytes)), - 12); - - // the elements of out excluding the last element if it happens to be a high surrogate - const auto out = _mm512_ternarylogic_epi32(last_bytes, second_last_bytes, third_last_bytes, 254); - // Encodings out of range - { - // the location of 3-byte sequence start bytes in the input code units in word_out corresponding to 3-byte sequences. - const auto m3 = static_cast<__mmask32>(_pext_u64((mask_byte_34 & (remaining_mask ^ mask_byte_4)) << 2, mend)); - const auto mask_out_less_than_0x800 = _mm512_mask_cmplt_epu16_mask(m3, out, v_0800_0800); - const auto mask_out_minus_0x800 = _mm512_sub_epi16(out, v_d800_d800); - const auto mask_out_too_small = _mm512_mask_cmplt_epu16_mask(m3, mask_out_minus_0x800, v_0800_0800); - if (_kor_mask32(mask_out_less_than_0x800, mask_out_too_small) != 0) { return false; } - } - - // we adjust mend at the end of the output. - const auto mask_processed = [mend, remaining_mask]() noexcept -> auto - { - if constexpr (MaskOut) { return _pdep_u64(0xffff'ffff, _kand_mask64(mend, remaining_mask)); } - else - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'remaining_mask' is not used [-Werror,-Wunused-lambda-capture] - (void)remaining_mask; - #endif - - return _pdep_u64(0xffff'ffff, mend); - } - }(); - - const auto num_out = std::popcount(mask_processed); - - if constexpr (is_big_endian) - { - _mm512_mask_storeu_epi16(it_output_current, static_cast<__mmask32>((__mmask64{1} << num_out) - 1), _mm512_shuffle_epi8(out, byte_flip)); - } - else { _mm512_mask_storeu_epi16(it_output_current, static_cast<__mmask32>((__mmask64{1} << num_out) - 1), out); } - - it_input_current += 64 - std::countl_zero(mask_processed); - it_output_current += num_out; - return true; - } - - // We have a 4-byte sequence, this is the general case. - const auto mask_pattern_3 = _kshiftli_mask64(mask_byte_4, 3); - // expected continuation bytes - const auto mask_combing = _kor_mask64(_kor_mask64(mask_pattern_1, mask_patten_2), mask_pattern_3); - const auto mask_byte_1234 = _kor_mask64(mask_byte_1, mask_byte_234); - - // identifying the last bytes of each sequence to be decoded - const auto mend = [mask_byte_1234, mask_pattern_3, remaining]() noexcept -> auto - { - if constexpr (MaskOut) - { - return _kor_mask64( - _kor_mask64(_kshiftri_mask64(_kor_mask64(mask_pattern_3, mask_byte_1234), 1), mask_pattern_3), - __mmask64{1} << (remaining - 1)); - } - else - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'remaining' is not used [-Werror,-Wunused-lambda-capture] - (void)remaining; - #endif - - return _kor_mask64(_kshiftri_mask64(_kor_mask64(mask_pattern_3, mask_byte_1234), 1), mask_pattern_3); - } - }(); - - const auto last_and_third = _mm512_maskz_compress_epi8(mend, mask_identity); - const auto last_and_third_u16 = _mm512_cvtepu8_epi16(_mm512_castsi512_si256(last_and_third)); - // ASCII: 00000000 other: 11000000 - const auto non_ascii_tags = _mm512_maskz_mov_epi8(mask_not_ascii, v_c0c0_c0c0); - // high two bits cleared where not ASCII - const auto cleared_bytes = _mm512_andnot_si512(non_ascii_tags, in); - // bytes that precede non-ASCII bytes - const auto mask_before_non_ascii = _kshiftri_mask64(mask_not_ascii, 1); - const auto before_ascii_bytes = _mm512_maskz_mov_epi8(mask_before_non_ascii, cleared_bytes); - // the last byte of each character - const auto last_bytes = _mm512_maskz_permutexvar_epi8(0x5555'5555'5555'5555, last_and_third_u16, cleared_bytes); - - // indices of the second last bytes - const auto index_of_second_last_bytes = _mm512_add_epi16(v_ffff_ffff, last_and_third_u16); - // shifted into position - const auto second_last_bytes = _mm512_slli_epi16( - // the second last bytes (of two, three byte seq, surrogates) - _mm512_maskz_permutexvar_epi8(0x5555'5555'5555'5555, index_of_second_last_bytes, before_ascii_bytes), - 6); - - // indices of the third last bytes - const auto index_of_third_last_bytes = _mm512_add_epi16(v_ffff_ffff, index_of_second_last_bytes); - // shifted into position - const auto third_last_bytes = _mm512_slli_epi16( - // the third last bytes (of three byte sequences, high surrogate) - _mm512_maskz_permutexvar_epi8( - 0x5555'5555'5555'5555, - index_of_third_last_bytes, - // only those that are the third last byte of a sequence - _mm512_maskz_mov_epi8(mask_byte_34, cleared_bytes) - ), - 12); - - const auto third_second_and_last_bytes = - _mm512_ternarylogic_epi32(last_bytes, second_last_bytes, third_last_bytes, 254); - const auto mask_mp3_64 = _pext_u64(mask_pattern_3, mend); - const auto mask_mp3_low = static_cast<__mmask32>(mask_mp3_64); - const auto mask_mp3_high = static_cast<__mmask32>(mask_mp3_64 >> 1); - // low surrogate: 1101110000000000, other: 0000000000000000 - const auto mask_low_surrogate = _mm512_maskz_mov_epi16(mask_mp3_low, v_dc00_dc00); - const auto tagged_low_surrogate = _mm512_or_si512(third_second_and_last_bytes, mask_low_surrogate); - const auto shifted4_third_second_and_last_bytes = _mm512_srli_epi16(third_second_and_last_bytes, 4); - - // the elements of out excluding the last element if it happens to be a high surrogate - const auto out = _mm512_mask_add_epi16( - tagged_low_surrogate, - mask_mp3_high, - shifted4_third_second_and_last_bytes, - v_d7c0_d7c0 - ); - - // mismatched continuation bytes - if constexpr (MaskOut) { if (mask_combing != _kxor_mask64(remaining_mask, mask_byte_1234)) { return false; } } - else - { - // XNOR of mask_combing and mask_byte_1234 should be all zero if they differ the presence of a 1 bit indicates that they overlap. - if (const auto v = _kxnor_mask64(mask_combing, mask_byte_1234); not _kortestz_mask64_u8(v, v)) { return false; } - } - - // Encodings out of range - { - // the location of 3-byte sequence start bytes in the input code units in word_out corresponding to 3-byte sequences. - const auto m3 = static_cast<__mmask32>(_pext_u64(mask_byte_34 & (remaining_mask ^ mask_byte_4) << 2, mend)); - const auto mask_out_less_than_0x800 = _mm512_mask_cmplt_epu16_mask(m3, out, v_0800_0800); - const auto mask_out_minus_0x800 = _mm512_sub_epi16(out, v_d800_d800); - const auto mask_out_too_small = _mm512_mask_cmplt_epu16_mask(m3, mask_out_minus_0x800, v_0800_0800); - const auto mask_out_greater_equal_0x400 = _mm512_mask_cmpge_epu16_mask(mask_mp3_high, mask_out_minus_0x800, v_0400_0400); - if (_kortestz_mask32_u8(mask_out_greater_equal_0x400, _kor_mask32(mask_out_less_than_0x800, mask_out_too_small)) != 0) { return false; } - } - - // we adjust mend at the end of the output. - const auto mask_processed = [m = ~(mask_mp3_high & 0x8000'0000), mend, remaining_mask]() noexcept -> auto - { - if constexpr (MaskOut) { return _pdep_u64(m, _kand_mask64(mend, remaining_mask)); } - else - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'remaining_mask' is not used [-Werror,-Wunused-lambda-capture] - (void)remaining_mask; - #endif - - return _pdep_u64(m, mend); - } - }(); - - const auto num_out = std::popcount(mask_processed); - - if constexpr (is_big_endian) - { - _mm512_mask_storeu_epi16( - it_output_current, - (__mmask32{1} << num_out) - 1, - _mm512_shuffle_epi8(out, byte_flip) - ); - } - else { _mm512_mask_storeu_epi16(it_output_current, (__mmask32{1} << num_out) - 1, out); } - - it_input_current += 64 - std::countl_zero(mask_processed); - it_output_current += num_out; - return true; - } - - // all ASCII or 2 byte - const auto continuation_or_ascii = [mask_byte_234, remaining_mask]() noexcept -> auto - { - if constexpr (MaskOut) { return _kand_mask64(_knot_mask64(mask_byte_234), remaining_mask); } - else - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'remaining_mask' is not used [-Werror,-Wunused-lambda-capture] - (void)remaining_mask; - #endif - - return _knot_mask64(mask_byte_234); - } - }(); - - // on top of -0xc0 we subtract -2 which we get back later of the continuation byte tags - const auto leading_two_bytes = _mm512_maskz_sub_epi8(mask_byte_234, in, v_c2c2_c2c2); - const auto leading_mask = [mask_byte_1, mask_byte_234, remaining_mask]() noexcept -> auto - { - if constexpr (MaskOut) { return _kand_mask64(_kor_mask64(mask_byte_1, mask_byte_234), remaining_mask); } - else - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'remaining_mask' is not used [-Werror,-Wunused-lambda-capture] - (void)remaining_mask; - #endif - - return _kor_mask64(mask_byte_1, mask_byte_234); - } - }(); - - if constexpr (MaskOut) - { - if (_kshiftli_mask64(mask_byte_234, 1) != _kxor_mask64(remaining_mask, leading_mask)) { return false; } - } - else - { - if (const auto v = _kxnor_mask64(_kshiftli_mask64(mask_byte_234, 1), leading_mask); not _kortestz_mask64_u8(v, v)) - { - return false; - } - } - - if constexpr (MaskOut) - { - it_input_current += 64 - std::countl_zero(_pdep_u64(0xffff'ffff, continuation_or_ascii)); - } - else - { - // In the two-byte/ASCII scenario, we are easily latency bound, - // so we want to increment the input buffer as quickly as possible. - // We process 32 bytes unless the byte at index 32 is a continuation byte, - // in which case we include it as well for a total of 33 bytes. - // Note that if x is an ASCII byte, then the following is false: - // int8_t(x) <= int8_t(0xc0) under two's complement. - it_input_current += 32; - if (static_cast(*it_input_current) <= static_cast(0xc0)) - { - it_input_current += 1; - } - } - - const auto out = [&]() noexcept -> auto - { - const auto lead = _mm512_slli_epi16(_mm512_cvtepu8_epi16(_mm512_castsi512_si256(_mm512_maskz_compress_epi8(leading_mask, leading_two_bytes))), 6); - - const auto follow = _mm512_cvtepu8_epi16(_mm512_castsi512_si256(_mm512_maskz_compress_epi8(continuation_or_ascii, in))); - - if constexpr (is_big_endian) { return _mm512_shuffle_epi8(_mm512_add_epi16(follow, lead), byte_flip); } - else { return _mm512_add_epi16(follow, lead); } - }(); - - if constexpr (MaskOut) - { - const auto num_out = std::popcount(_pdep_u64(0xffff'ffff, leading_mask)); - _mm512_mask_storeu_epi16(it_output_current, (__mmask32{1} << num_out) - 1, out); - - it_output_current += num_out; - } - else - { - const auto num_out = std::popcount(leading_mask); - _mm512_mask_storeu_epi16(it_output_current, (__mmask32{1} << num_out) - 1, out); - - it_output_current += num_out; - } - - return true; - }; - - bool success = true; - while (success) - { - if (it_input_current + 64 <= it_input_end) - { - success = process.template operator()(it_input_end - it_input_current); - } - else if (it_input_current < it_input_end) - { - success = process.template operator()(it_input_end - it_input_current); - } - else { break; } - } - - if (not success) - { - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - auto result = - scalar_type::template rewind_and_convert( - it_input_begin, - {it_input_current, static_cast(it_input_end - it_input_current)}, - it_output_current - ); - result.count += (it_input_current - it_input_begin); - if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) { return result; } - else { return result.count; } - } - else { return 0; } - } - } - else if constexpr (OutputCategory == CharsCategory::UTF32) - { - avx512_utf8_checker checker{}; - const auto process = [&it_input_current, it_input_end, &it_output_current, byte_flip, &checker]() noexcept -> bool - { - // In the main loop, we consume 64 bytes per iteration, but we access 64 + 4 bytes. - // We check for it_input_current + 64 + 64 <= it_input_end because - // we want to do mask-less writes without overruns. - while (it_input_current + 64 + 64 <= it_input_end) - { - const auto in = _mm512_loadu_si512(it_input_current); - if (checker.check_input(in)) - { - it_output_current += icelake_utf8_detail::store_ascii(in, byte_flip, it_output_current); - it_input_current += 64; - continue; - } - - if (checker.has_error()) { return false; } - - const auto lane_0 = icelake_utf8_detail::broadcast<0>(in); - const auto lane_1 = icelake_utf8_detail::broadcast<1>(in); - const auto lane_2 = icelake_utf8_detail::broadcast<2>(in); - const auto lane_3 = icelake_utf8_detail::broadcast<3>(in); - const auto lane_4 = _mm512_set1_epi32(static_cast(memory::unaligned_load(it_input_current + 64))); - - #if defined(GAL_PROMETHEUS_COMPILER_GNU) - int valid_count_0 = 0; - auto vec_0 = icelake_utf8_detail::expand_and_identify(lane_0, lane_1, valid_count_0); - int valid_count_1 = 0; - auto vec_1 = icelake_utf8_detail::expand_and_identify(lane_1, lane_2, valid_count_1);// NOLINT(readability-suspicious-call-argument) - #else - auto [vec_0, valid_count_0] = icelake_utf8_detail::expand_and_identify(lane_0, lane_1); - auto [vec_1, valid_count_1] = icelake_utf8_detail::expand_and_identify(lane_1, lane_2); // NOLINT(readability-suspicious-call-argument) - #endif - - if (valid_count_0 + valid_count_1 <= 16) - { - vec_0 = _mm512_mask_expand_epi32( - vec_0, - static_cast<__mmask16>(((1 << valid_count_1) - 1) << valid_count_0), - vec_1 - ); - valid_count_0 += valid_count_1; - vec_0 = icelake_utf8_detail::expand_utf8_to_utf32(vec_0); - - it_output_current += icelake_utf8_detail::store_utf16_or_utf32( - vec_0, - byte_flip, - valid_count_0, - it_output_current - ); - } - else - { - vec_0 = icelake_utf8_detail::expand_utf8_to_utf32(vec_0); - vec_1 = icelake_utf8_detail::expand_utf8_to_utf32(vec_1); - - it_output_current += icelake_utf8_detail::store_utf16_or_utf32( - vec_0, - byte_flip, - valid_count_0, - it_output_current - ); - it_output_current += icelake_utf8_detail::store_utf16_or_utf32( - vec_1, - byte_flip, - valid_count_1, - it_output_current - ); - } - - #if defined(GAL_PROMETHEUS_COMPILER_GNU) - int valid_count_2 = 0; - auto vec_2 = icelake_utf8_detail::expand_and_identify(lane_2, lane_3, valid_count_2); - int valid_count_3 = 0; - auto vec_3 = icelake_utf8_detail::expand_and_identify(lane_3, lane_4, valid_count_3); - #else - auto [vec_2, valid_count_2] = icelake_utf8_detail::expand_and_identify(lane_2, lane_3); - auto [vec_3, valid_count_3] = icelake_utf8_detail::expand_and_identify(lane_3, lane_4); - #endif - - if (valid_count_2 + valid_count_3 <= 16) - { - vec_2 = _mm512_mask_expand_epi32( - vec_2, - static_cast<__mmask16>(((1 << valid_count_3) - 1) << valid_count_2), - vec_3 - ); - valid_count_2 += valid_count_3; - vec_2 = icelake_utf8_detail::expand_utf8_to_utf32(vec_2); - - it_output_current += icelake_utf8_detail::store_utf16_or_utf32( - vec_2, - byte_flip, - valid_count_2, - it_output_current - ); - } - else - { - vec_2 = icelake_utf8_detail::expand_utf8_to_utf32(vec_2); - vec_3 = icelake_utf8_detail::expand_utf8_to_utf32(vec_3); - - it_output_current += icelake_utf8_detail::store_utf16_or_utf32( - vec_2, - byte_flip, - valid_count_2, - it_output_current - ); - it_output_current += icelake_utf8_detail::store_utf16_or_utf32( - vec_3, - byte_flip, - valid_count_3, - it_output_current - ); - } - - it_input_current += 4 * 16; - } - - auto it_valid_input_current = it_input_current; - - // For the final pass, we validate 64 bytes, - // but we only transcode 3*16 bytes, - // so we may end up double-validating 16 bytes. - if (it_input_current + 64 <= it_input_end) - { - if (const auto in = _mm512_loadu_si512(it_input_current); - checker.check_input(in)) - { - it_output_current += icelake_utf8_detail::store_ascii(in, byte_flip, it_output_current); - it_input_current += 64; - } - else if (checker.has_error()) { return false; } - else - { - const auto lane_0 = icelake_utf8_detail::broadcast<0>(in); - const auto lane_1 = icelake_utf8_detail::broadcast<1>(in); - const auto lane_2 = icelake_utf8_detail::broadcast<2>(in); - const auto lane_3 = icelake_utf8_detail::broadcast<3>(in); - - #if defined(GAL_PROMETHEUS_COMPILER_GNU) - int valid_count_0 = 0; - auto vec_0 = icelake_utf8_detail::expand_and_identify(lane_0, lane_1, valid_count_0); - int valid_count_1 = 0; - auto vec_1 = icelake_utf8_detail::expand_and_identify(lane_1, lane_2, valid_count_1);// NOLINT(readability-suspicious-call-argument) - #else - auto [vec_0, valid_count_0] = icelake_utf8_detail::expand_and_identify(lane_0, lane_1); - auto [vec_1, valid_count_1] = icelake_utf8_detail::expand_and_identify(lane_1, lane_2); // NOLINT(readability-suspicious-call-argument) - #endif - - if (valid_count_0 + valid_count_1 <= 16) - { - vec_0 = _mm512_mask_expand_epi32( - vec_0, - static_cast<__mmask16>(((1 << valid_count_1) - 1) << valid_count_0), - vec_1 - ); - valid_count_0 += valid_count_1; - vec_0 = icelake_utf8_detail::expand_utf8_to_utf32(vec_0); - - it_output_current += icelake_utf8_detail::store_utf16_or_utf32( - vec_0, - byte_flip, - valid_count_0, - it_output_current - ); - } - else - { - vec_0 = icelake_utf8_detail::expand_utf8_to_utf32(vec_0); - vec_1 = icelake_utf8_detail::expand_utf8_to_utf32(vec_1); - - it_output_current += icelake_utf8_detail::store_utf16_or_utf32( - vec_0, - byte_flip, - valid_count_0, - it_output_current - ); - it_output_current += icelake_utf8_detail::store_utf16_or_utf32( - vec_1, - byte_flip, - valid_count_1, - it_output_current - ); - } - - it_output_current += icelake_utf8_detail::transcode_16(lane_2, lane_3, byte_flip, it_output_current); - it_input_current += 3 * 16; - } - it_valid_input_current += 4 * 16; - } - - { - const auto in = _mm512_maskz_loadu_epi8( - (__mmask64{1} << (it_input_end - it_valid_input_current)) - 1, - it_valid_input_current - ); - checker.check_input(in); - } - checker.check_eof(); - return not checker.has_error(); - }; - - if ( - const auto success = process(); - not success - ) - { - auto result = - scalar_type::template rewind_and_convert( - it_input_begin, - {it_input_current, static_cast(it_input_end - it_input_current)}, - it_output_current - ); - result.count += (it_input_current - it_input_begin); - if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) { return result; } - else { return result.count; } - } - - if (it_input_current != it_input_end) - { - // the AVX512 procedure looks up 4 bytes forward, - // and correctly converts multibyte chars even if their continuation bytes lie outside 16-byte window. - // It means, we have to skip continuation bytes from the beginning it_input_current, as they were already consumed. - while (it_input_current != it_input_end and ((*it_input_current & 0xc0) == 0x80)) { it_input_current += 1; } - - if (it_input_current != it_input_end) - { - auto result = scalar_type::template convert( - {it_input_current, static_cast(it_input_end - it_input_current)}, - it_output_current - ); - - result.count += (it_input_current - it_input_begin); - if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) { return result; } - else { return result.count; } - } - } - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - else - { - const auto v_0080 = _mm512_set1_epi8(static_cast(0x80)); - - // In the main loop, we consume 64 bytes per iteration, but we access 64 + 4 bytes. - // We check for it_input_current + 64 + 64 <= it_input_end because - // we want to do mask-less writes without overruns. - while (it_input_current + 64 + 64 <= it_input_end) - { - const auto in = _mm512_loadu_si512(it_input_current); - - if (const auto ascii = _mm512_test_epi8_mask(in, v_0080); - ascii == 0) - { - it_output_current += icelake_utf8_detail::store_ascii(in, byte_flip, it_output_current); - it_input_current += 64; - continue; - } - - const auto lane_0 = icelake_utf8_detail::broadcast<0>(in); - const auto lane_1 = icelake_utf8_detail::broadcast<1>(in); - const auto lane_2 = icelake_utf8_detail::broadcast<2>(in); - const auto lane_3 = icelake_utf8_detail::broadcast<3>(in); - const auto lane_4 = _mm512_set1_epi32(static_cast(memory::unaligned_load(it_input_current + 64))); - - #if defined(GAL_PROMETHEUS_COMPILER_GNU) - int valid_count_0 = 0; - auto vec_0 = icelake_utf8_detail::expand_and_identify(lane_0, lane_1, valid_count_0); - int valid_count_1 = 0; - auto vec_1 = icelake_utf8_detail::expand_and_identify(lane_1, lane_2, valid_count_1);// NOLINT(readability-suspicious-call-argument) - #else - auto [vec_0, valid_count_0] = icelake_utf8_detail::expand_and_identify(lane_0, lane_1); - auto [vec_1, valid_count_1] = icelake_utf8_detail::expand_and_identify(lane_1, lane_2); // NOLINT(readability-suspicious-call-argument) - #endif - - if (valid_count_0 + valid_count_1 <= 16) - { - vec_0 = _mm512_mask_expand_epi32( - vec_0, - static_cast<__mmask16>(((1 << valid_count_1) - 1) << valid_count_0), - vec_1 - ); - valid_count_0 += valid_count_1; - vec_0 = icelake_utf8_detail::expand_utf8_to_utf32(vec_0); - - it_output_current += icelake_utf8_detail::store_utf16_or_utf32( - vec_0, - byte_flip, - valid_count_0, - it_output_current - ); - } - else - { - vec_0 = icelake_utf8_detail::expand_utf8_to_utf32(vec_0); - vec_1 = icelake_utf8_detail::expand_utf8_to_utf32(vec_1); - - it_output_current += icelake_utf8_detail::store_utf16_or_utf32( - vec_0, - byte_flip, - valid_count_0, - it_output_current - ); - it_output_current += icelake_utf8_detail::store_utf16_or_utf32( - vec_1, - byte_flip, - valid_count_1, - it_output_current - ); - } - - #if defined(GAL_PROMETHEUS_COMPILER_GNU) - int valid_count_2 = 0; - auto vec_2 = icelake_utf8_detail::expand_and_identify(lane_2, lane_3, valid_count_2); - int valid_count_3 = 0; - auto vec_3 = icelake_utf8_detail::expand_and_identify(lane_3, lane_4, valid_count_3); - #else - auto [vec_2, valid_count_2] = icelake_utf8_detail::expand_and_identify(lane_2, lane_3); - auto [vec_3, valid_count_3] = icelake_utf8_detail::expand_and_identify(lane_3, lane_4); - #endif - - if (valid_count_2 + valid_count_3 <= 16) - { - vec_2 = _mm512_mask_expand_epi32( - vec_2, - static_cast<__mmask16>(((1 << valid_count_3) - 1) << valid_count_2), - vec_3 - ); - valid_count_2 += valid_count_3; - vec_2 = icelake_utf8_detail::expand_utf8_to_utf32(vec_2); - - it_output_current += icelake_utf8_detail::store_utf16_or_utf32( - vec_2, - byte_flip, - valid_count_2, - it_output_current - ); - } - else - { - vec_2 = icelake_utf8_detail::expand_utf8_to_utf32(vec_2); - vec_3 = icelake_utf8_detail::expand_utf8_to_utf32(vec_3); - - it_output_current += icelake_utf8_detail::store_utf16_or_utf32( - vec_2, - byte_flip, - valid_count_2, - it_output_current - ); - it_output_current += icelake_utf8_detail::store_utf16_or_utf32( - vec_3, - byte_flip, - valid_count_3, - it_output_current - ); - } - - it_input_current += 4 * 16; - } - - if (it_input_current + 64 <= it_input_current) - { - const auto in = _mm512_loadu_si512(it_input_current); - - if (const auto ascii = _mm512_test_epi8_mask(in, v_0080); - ascii == 0) - { - it_output_current += icelake_utf8_detail::store_ascii(in, byte_flip, it_output_current); - it_input_current += 64; - } - else - { - const auto lane_0 = icelake_utf8_detail::broadcast<0>(in); - const auto lane_1 = icelake_utf8_detail::broadcast<1>(in); - const auto lane_2 = icelake_utf8_detail::broadcast<2>(in); - const auto lane_3 = icelake_utf8_detail::broadcast<3>(in); - - #if defined(GAL_PROMETHEUS_COMPILER_GNU) - int valid_count_0 = 0; - auto vec_0 = icelake_utf8_detail::expand_and_identify(lane_0, lane_1, valid_count_0); - int valid_count_1 = 0; - auto vec_1 = icelake_utf8_detail::expand_and_identify(lane_1, lane_2, valid_count_1);// NOLINT(readability-suspicious-call-argument) - #else - auto [vec_0, valid_count_0] = icelake_utf8_detail::expand_and_identify(lane_0, lane_1); - auto [vec_1, valid_count_1] = icelake_utf8_detail::expand_and_identify(lane_1, lane_2); // NOLINT(readability-suspicious-call-argument) - #endif - - if (valid_count_0 + valid_count_1 <= 16) - { - vec_0 = _mm512_mask_expand_epi32( - vec_0, - static_cast<__mmask16>(((1 << valid_count_1) - 1) << valid_count_0), - vec_1 - ); - valid_count_0 += valid_count_1; - vec_0 = icelake_utf8_detail::expand_utf8_to_utf32(vec_0); - - it_output_current += icelake_utf8_detail::store_utf16_or_utf32( - vec_0, - byte_flip, - valid_count_0, - it_output_current - ); - } - else - { - vec_0 = icelake_utf8_detail::expand_utf8_to_utf32(vec_0); - vec_1 = icelake_utf8_detail::expand_utf8_to_utf32(vec_1); - - it_output_current += icelake_utf8_detail::store_utf16_or_utf32( - vec_0, - byte_flip, - valid_count_0, - it_output_current - ); - it_output_current += icelake_utf8_detail::store_utf16_or_utf32( - vec_1, - byte_flip, - valid_count_1, - it_output_current - ); - } - - it_output_current += icelake_utf8_detail::transcode_16(lane_2, lane_3, byte_flip, it_output_current); - it_input_current += 3 * 16; - } - } - - if (it_input_current != it_input_end) - { - // the AVX512 procedure looks up 4 bytes forward, - // and correctly converts multibyte chars even if their continuation bytes lie outside 16-byte window. - // It means, we have to skip continuation bytes from the beginning it_input_current, as they were already consumed. - while (it_input_current != it_input_end and ((*it_input_current & 0xc0) == 0x80)) { it_input_current += 1; } - - if (it_input_current != it_input_end) - { - auto result = scalar_type::template convert( - {it_input_current, static_cast(it_input_end - it_input_current)}, - it_output_current - ); - - result.count += (it_input_current - it_input_begin); - if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) { return result; } - else { return result.count; } - } - } - } - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE("Unknown or unsupported `OutputCategory` (we don't know the `endian` by UTF16, so it's not allowed to use it here)."); - } - - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { return static_cast(it_output_current - it_output_begin); } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::NONE, .count = static_cast(it_input_current - it_input_begin)}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert( - const pointer_type input, - typename output_type::pointer output - ) noexcept -> std::conditional_t - { - return convert({input, std::char_traits::length(input)}, output); - } - - template< - typename StringType, - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - requires requires(StringType& string) - { - string.resize(std::declval()); - { - string.data() - } -> std::convertible_to::pointer>; - } - [[nodiscard]] constexpr static auto convert(const input_type input) noexcept -> StringType - { - StringType result{}; - result.resize(length(input)); - - (void)convert(input, result.data()); - return result; - } - - template< - typename StringType, - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - requires requires(StringType& string) - { - string.resize(std::declval()); - { - string.data() - } -> std::convertible_to::pointer>; - } - [[nodiscard]] constexpr static auto convert(const pointer_type input) noexcept -> StringType - { - StringType result{}; - result.resize(length(input)); - - return convert({input, std::char_traits::length(input)}, result.data()); - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert(const input_type input) noexcept -> std::basic_string::value_type> - { - std::basic_string::value_type> result{}; - result.resize(length(input)); - - (void)convert(input, result.data()); - return result; - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert(const pointer_type input) noexcept -> std::basic_string::value_type> - { - std::basic_string::value_type> result{}; - result.resize(length(input)); - - return convert({input, std::char_traits::length(input)}, result.data()); - } - }; - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - - using icelake_utf8_detail::avx512_utf8_checker; - - template<> - class Simd<"icelake.utf8"> : public icelake_utf8_detail::SimdUtf8Base {}; - - template<> - class Simd<"icelake.utf8_char"> : public icelake_utf8_detail::SimdUtf8Base {}; - - template<> - struct simd_processor_of - { - using type = Simd<"icelake.utf8">; - }; - - template<> - struct simd_processor_of - { - using type = Simd<"icelake.utf8_char">; - }; - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -} diff --git a/src/chars/scalar.cpp b/src/chars/scalar.cpp new file mode 100644 index 00000000..64472710 --- /dev/null +++ b/src/chars/scalar.cpp @@ -0,0 +1,5954 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include +#include + +#include + +#include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace +{ + using namespace gal::prometheus; + using namespace chars; + + using data_type = std::uint64_t; + + namespace common + { + template + requires (std::is_same_v or std::is_same_v) + [[nodiscard]] constexpr auto to_native_utf16(const ValueType value) noexcept -> std::uint16_t + { + if constexpr (SourceEndian != std::endian::native) + { + return std::byteswap(value); + } + else + { + return value; + } + } + + template + requires (Type == CharsType::UTF16_LE or Type == CharsType::UTF16_BE) and + (std::is_same_v or std::is_same_v) + [[nodiscard]] constexpr auto to_native_utf16(const ValueType value) noexcept -> std::uint16_t + { + return common::to_native_utf16(value); + } + + template + [[nodiscard]] constexpr auto to_char(const auto value) noexcept -> typename output_type_of::value_type + { + using char_type = typename output_type_of::value_type; + + if constexpr ( + OutputType == CharsType::UTF16_LE or + OutputType == CharsType::UTF16_BE + ) + { + return static_cast(common::to_native_utf16(static_cast(value))); + } + else + { + return static_cast(value); + } + } + + template + requires ( + InputType == CharsType::LATIN or + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto sign_of(const data_type data) noexcept -> auto + { + struct sign_type + { + private: + data_type data_; + + public: + constexpr explicit sign_type(const data_type d) noexcept + : data_{d} {} + + /** + * @brief Get the underlying mask of the current block. + */ + [[nodiscard]] constexpr auto mask() const noexcept -> std::uint8_t + { + // MSB => LSB + const auto msb = (data_ >> 7) & static_cast(0x01'01'01'01'01'01'01'01); + const auto packed = msb * static_cast(0x01'02'04'08'10'20'40'80); + const auto mask = static_cast(packed >> 56); + + return mask; + } + + /** + * @brief Whether all sign bits are 0, in other words, whether the current block is all ASCII. + */ + [[nodiscard]] constexpr auto pure() const noexcept -> bool + { + return (data_ & 0x8080'8080'8080'8080) == 0; + } + + /** + * @brief Get the number of non-ASCII in the current block. + */ + [[nodiscard]] constexpr auto count() const noexcept -> std::size_t + { + // const auto mask = this->mask(data_); + // const auto count = std::popcount(mask); + // return count; + + // MSB => LSB + const auto msb = (data_ >> 7) & static_cast(0x01'01'01'01'01'01'01'01); + + // const auto packed = msb * static_cast(0x01'01'01'01'01'01'01'01); + // const auto count = static_cast(packed >> 56); + + const auto count = std::popcount(msb); + return count; + } + + /** + * @brief Get the number of consecutive ASCII at the beginning. + * [ascii] [non-ascii] [?] [?] ... Xn ... [?] [?] [ascii] [ascii] + * ^-----^ start_count + * ^----------^ end_count + */ + [[nodiscard]] constexpr auto start_count() const noexcept -> std::size_t + { + const auto mask = this->mask(); + + return std::countr_zero(mask); + } + + /** + * @brief Get the number of consecutive ASCII at the ending. + * [ascii] [non-ascii] [?] [?] ... Xn ... [?] [?] [ascii] [ascii] + * ^-----^ start_count + * ^----------^ end_count + */ + [[nodiscard]] constexpr auto end_count() const noexcept -> std::size_t + { + const auto mask = this->mask(); + + return std::countl_zero(mask); + } + }; + + return sign_type{data}; + } + } + + namespace latin + { + using input_type = chars::latin::input_type; + using char_type = chars::latin::char_type; + using size_type = chars::latin::size_type; + using pointer_type = chars::latin::pointer_type; + + [[nodiscard]] constexpr auto validate(const pointer_type current, const pointer_type end) noexcept -> std::pair + { + std::ignore = end; + constexpr std::ptrdiff_t length = 1; + + if (const auto value = static_cast(*(current + 0)); + static_cast(value) < 0x80) + { + return {length, ErrorCode::NONE}; + } + + return {length, ErrorCode::TOO_LARGE}; + } + + // 1 LATIN => 1/2 UTF-8 + template + requires ( + OutputType == CharsType::UTF8_CHAR or + OutputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto write_utf8( + typename output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + std::ignore = Correct; + std::ignore = end; + + constexpr std::ptrdiff_t length = 1; + + if constexpr (const auto value = static_cast(*(current + 0)); + Pure) + { + *(output + 0) = common::to_char(value); + + output += 1; + return {length, ErrorCode::NONE}; + } + else + { + if ((value & 0x80) == 0) + { + // ASCII + *(output + 0) = common::to_char(value); + + output += 1; + return {length, ErrorCode::NONE}; + } + + // 0b110?'???? 0b10??'???? + const auto c1 = static_cast((value >> 6) | 0b1100'0000); + const auto c2 = static_cast((value & 0b0011'1111) | 0b1000'0000); + + *(output + 0) = common::to_char(c1); + *(output + 1) = common::to_char(c2); + + output += 2; + return {length, ErrorCode::NONE}; + } + } + + // 1 LATIN => 1 UTF-16 + template + requires ( + OutputType == CharsType::UTF16_LE or + OutputType == CharsType::UTF16_BE + ) + [[nodiscard]] constexpr auto write_utf16( + typename output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + std::ignore = Pure; + std::ignore = Correct; + std::ignore = end; + + constexpr std::ptrdiff_t length = 1; + + const auto value = static_cast(*(current + 0)); + + *(output + 0) = common::to_char(value); + + output += 1; + return {length, ErrorCode::NONE}; + } + + // 1 LATIN => 1 UTF-32 + template + requires ( + OutputType == CharsType::UTF32 + ) + [[nodiscard]] constexpr auto write_utf32( + typename output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + std::ignore = Pure; + std::ignore = Correct; + std::ignore = end; + + constexpr std::ptrdiff_t length = 1; + + const auto value = static_cast(*(current + 0)); + + *(output + 0) = common::to_char(value); + + output += 1; + return {length, ErrorCode::NONE}; + } + + namespace scalar + { + template + [[nodiscard]] constexpr auto advance_of() noexcept -> std::ptrdiff_t + { + std::ignore = OutputType; + + return sizeof(data_type) / sizeof(char_type); + } + + template + [[nodiscard]] constexpr auto read(const pointer_type source) noexcept -> data_type + { + std::ignore = OutputType; + + return memory::unaligned_load(source); + } + + [[nodiscard]] constexpr auto validate(const input_type input) noexcept -> result_error_input_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + + constexpr auto advance = scalar::advance_of(); + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + while (it_input_current + advance <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance}; + #endif + + const auto data = scalar::read(it_input_current); + + if (const auto sign = common::sign_of(data); + not sign.pure()) + { + it_input_current += sign.start_count(); + + const auto current_input_length = static_cast(it_input_current - it_input_begin); + + return {.error = ErrorCode::TOO_LARGE, .input = current_input_length}; + } + + it_input_current += advance; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(remaining < advance); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto end = it_input_current + remaining; + while (it_input_current < end) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + + const auto [length, error] = ::latin::validate(it_input_current, it_input_end); + GAL_PROMETHEUS_ERROR_ASSUME(length == 1); + + if (error != ErrorCode::NONE) + { + return {.error = error, .input = current_input_length}; + } + + it_input_current += length; + } + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + return {.error = ErrorCode::NONE, .input = current_input_length}; + } + + template + [[nodiscard]] constexpr auto length(const input_type input) noexcept -> size_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + + if constexpr ( + OutputType == CharsType::UTF8_CHAR or + OutputType == CharsType::UTF8 + ) + { + constexpr auto advance = scalar::advance_of(); + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + size_type output_length = input_length; + + while (it_input_current + advance <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance}; + #endif + + const auto data = scalar::read(it_input_current); + + if (const auto sign = common::sign_of(data); + not sign.pure()) + { + output_length += sign.count(); + } + + it_input_current += advance; + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(remaining < advance); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + const auto end = it_input_current + remaining; + while (it_input_current < end) + { + const auto [length, error] = ::latin::validate(it_input_current, it_input_end); + GAL_PROMETHEUS_ERROR_ASSUME(length == 1); + + if (error != ErrorCode::NONE) + { + output_length += 1; + } + + it_input_current += length; + } + } + + return output_length; + } + // ReSharper disable CppClangTidyBugproneBranchClone + else if constexpr ( + OutputType == CharsType::UTF16_LE or + OutputType == CharsType::UTF16_BE or + OutputType == CharsType::UTF16 + ) + { + return input.size(); + } + else if constexpr ( + OutputType == CharsType::UTF32 + ) + { + return input.size(); + } + // ReSharper restore CppClangTidyBugproneBranchClone + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + template + requires ( + OutputType == CharsType::UTF8_CHAR or + OutputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto write_utf8( + const typename output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + const auto advance = scalar::advance_of(); + const auto transform = [&](const decltype(advance) n) noexcept -> void + { + const auto end = it_input_current + n; + while (it_input_current < end) + { + const auto [length, error] = ::latin::write_utf8(it_output_current, it_input_current, it_input_end); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length == 1); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(error == ErrorCode::NONE); + + it_input_current += length; + } + }; + + while (it_input_current + advance <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance}; + #endif + + const auto data = scalar::read(it_input_current); + + if constexpr (Pure) + { + transform.template operator()(advance); + } + else + { + if (const auto sign = common::sign_of(data); + not sign.pure()) + { + const auto start_count = sign.start_count(); + const auto end_count = sign.end_count(); + const auto unknown_count = advance - start_count - end_count; + + transform.template operator()(static_cast(start_count)); + transform.template operator()(static_cast(unknown_count)); + } + else + { + transform.template operator()(advance); + } + } + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(remaining < advance); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + transform.template operator()(remaining); + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + template + requires ( + OutputType == CharsType::UTF16_LE or + OutputType == CharsType::UTF16_BE + ) + [[nodiscard]] constexpr auto write_utf16( + const typename output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + const auto advance = scalar::advance_of(); + const auto transform = [&](const decltype(advance) n) noexcept -> void + { + const auto end = it_input_current + n; + while (it_input_current < end) + { + const auto [length, error] = ::latin::write_utf16(it_output_current, it_input_current, it_input_end); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length == 1); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(error == ErrorCode::NONE); + + it_input_current += length; + } + }; + + while (it_input_current + advance <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance}; + #endif + + const auto data = scalar::read(it_input_current); + + if constexpr (Pure) + { + transform.template operator()(advance); + } + else + { + if (const auto sign = common::sign_of(data); + not sign.pure()) + { + const auto start_count = sign.start_count(); + const auto end_count = sign.end_count(); + const auto unknown_count = advance - start_count - end_count; + + transform.template operator()(static_cast(start_count)); + transform.template operator()(static_cast(unknown_count)); + } + else + { + transform.template operator()(advance); + } + } + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(remaining < advance); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + transform.template operator()(remaining); + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + template + requires ( + OutputType == CharsType::UTF32 + ) + [[nodiscard]] constexpr auto write_utf32( + const typename output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + const auto advance = scalar::advance_of(); + const auto transform = [&](const decltype(advance) n) noexcept -> void + { + const auto end = it_input_current + n; + while (it_input_current < end) + { + const auto [length, error] = ::latin::write_utf32(it_output_current, it_input_current, it_input_end); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length == 1); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(error == ErrorCode::NONE); + + it_input_current += length; + } + }; + + while (it_input_current + advance <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance}; + #endif + + const auto data = scalar::read(it_input_current); + + if constexpr (Pure) + { + transform.template operator()(advance); + } + else + { + if (const auto sign = common::sign_of(data); + not sign.pure()) + { + const auto start_count = sign.start_count(); + const auto end_count = sign.end_count(); + const auto unknown_count = advance - start_count - end_count; + + transform.template operator()(static_cast(start_count)); + transform.template operator()(static_cast(unknown_count)); + } + else + { + transform.template operator()(advance); + } + } + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(remaining < advance); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + transform.template operator()(remaining); + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + } + } + + namespace utf8 + { + namespace detail + { + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto check_byte_1( + const typename input_type_of::const_pointer current + ) noexcept -> bool + { + const auto value = static_cast(*(current + 0)); + + return (value & 0x80) == 0; + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) and + ( + OutputType == CharsType::LATIN or + OutputType == CharsType::UTF16_LE or + OutputType == CharsType::UTF16_BE or + OutputType == CharsType::UTF32 + ) + [[nodiscard]] constexpr auto write_byte_1( + typename output_type_of::pointer& output, + const typename input_type_of::const_pointer current, + const typename input_type_of::const_pointer end + ) noexcept -> std::pair + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(check_byte_1(current)); + + std::ignore = Correct; + std::ignore = end; + + constexpr std::ptrdiff_t length = 1; + + const auto value = static_cast(*(current + 0)); + + *(output + 0) = common::to_char(value); + + output += 1; + return {length, ErrorCode::NONE}; + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto check_byte_2( + const typename input_type_of::const_pointer current + ) noexcept -> bool + { + const auto value = static_cast(*(current + 0)); + + return (value & 0b1110'0000) == 0b1100'0000; + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) and + ( + OutputType == CharsType::LATIN or + OutputType == CharsType::UTF16_LE or + OutputType == CharsType::UTF16_BE or + OutputType == CharsType::UTF32 + ) + [[nodiscard]] constexpr auto write_byte_2( + typename output_type_of::pointer& output, + const typename input_type_of::const_pointer current, + const typename input_type_of::const_pointer end + ) noexcept -> std::pair + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(check_byte_2(current)); + + // we have a two-byte UTF-8 + constexpr std::ptrdiff_t length = 2; + + // minimal bound checking + if (current + 1 >= end) + { + return {length, ErrorCode::TOO_SHORT}; + } + + const auto leading_byte = static_cast(*(current + 0)); + const auto next_byte = static_cast(*(current + 1)); + + // checks if the next byte is a valid continuation byte in UTF-8. + // A valid continuation byte starts with 10. + if ((next_byte & 0b1100'0000) != 0b1000'0000) + { + return {length, ErrorCode::TOO_SHORT}; + } + + // assembles the Unicode code point from the two bytes. + // It does this by discarding the leading 110 and 10 bits from the two bytes, + // shifting the remaining bits of the first byte, + // and then combining the results with a bitwise OR operation. + const auto code_point = static_cast( + (leading_byte & 0b0001'1111) << 6 | + (next_byte & 0b0011'1111) + ); + + if constexpr (not Correct) + { + if (code_point < 0x80) + { + return {length, ErrorCode::OVERLONG}; + } + + if (code_point > + []() noexcept -> std::uint32_t + { + if constexpr (OutputType == CharsType::LATIN) { return 0xff; } + else { return 0x7ff; } + }() + ) + { + return {length, ErrorCode::TOO_LARGE}; + } + } + + *(output + 0) = common::to_char(code_point); + + output += 1; + return {length, ErrorCode::NONE}; + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto check_byte_3( + const typename input_type_of::const_pointer current + ) noexcept -> bool + { + const auto value = static_cast(*(current + 0)); + + return (value & 0b1111'0000) == 0b1110'0000; + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) and + ( + OutputType == CharsType::UTF16_LE or + OutputType == CharsType::UTF16_BE or + OutputType == CharsType::UTF32 + ) + [[nodiscard]] constexpr auto write_byte_3( + typename output_type_of::pointer& output, + const typename input_type_of::const_pointer current, + const typename input_type_of::const_pointer end + ) noexcept -> std::pair + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(check_byte_3(current)); + + // we have a three-byte UTF-8 + constexpr std::ptrdiff_t length = 3; + + // minimal bound checking + if (current + 2 >= end) + { + return {length, ErrorCode::TOO_SHORT}; + } + + const auto leading_byte = static_cast(*(current + 0)); + const auto next_byte_1 = static_cast(*(current + 1)); + const auto next_byte_2 = static_cast(*(current + 2)); + + if constexpr (not Correct) + { + if ( + ((next_byte_1 & 0b1100'0000) != 0b1000'0000) or + ((next_byte_2 & 0b1100'0000) != 0b1000'0000) + ) + { + return {length, ErrorCode::TOO_SHORT}; + } + } + + const auto code_point = static_cast( + (leading_byte & 0b0000'1111) << 12 | + (next_byte_1 & 0b0011'1111) << 6 | + (next_byte_2 & 0b0011'1111) + ); + + if constexpr (not Correct) + { + if (code_point < 0x800) + { + return {length, ErrorCode::OVERLONG}; + } + + if (code_point > 0xffff) + { + return {length, ErrorCode::TOO_LARGE}; + } + + if (code_point > 0xd7ff and code_point < 0xe000) + { + return {length, ErrorCode::SURROGATE}; + } + } + + *(output + 0) = common::to_char(code_point); + + output += 1; + return {length, ErrorCode::NONE}; + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto check_byte_4( + const typename input_type_of::const_pointer current + ) noexcept -> bool + { + const auto value = static_cast(*(current + 0)); + + return (value & 0b1111'1000) == 0b1111'0000; + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) and + ( + OutputType == CharsType::UTF16_LE or + OutputType == CharsType::UTF16_BE or + OutputType == CharsType::UTF32 + ) + [[nodiscard]] constexpr auto write_byte_4( + typename output_type_of::pointer& output, + const typename input_type_of::const_pointer current, + const typename input_type_of::const_pointer end + ) noexcept -> std::pair + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(check_byte_4(current)); + + // we have a four-byte UTF-8 word + constexpr std::ptrdiff_t length = 4; + + // minimal bound checking + if (current + 3 >= end) + { + return {length, ErrorCode::TOO_SHORT}; + } + + const auto leading_byte = static_cast(*(current + 0)); + const auto next_byte_1 = static_cast(*(current + 1)); + const auto next_byte_2 = static_cast(*(current + 2)); + const auto next_byte_3 = static_cast(*(current + 3)); + + if constexpr (not Correct) + { + if ( + ((next_byte_1 & 0b1100'0000) != 0b1000'0000) or + ((next_byte_2 & 0b1100'0000) != 0b1000'0000) or + ((next_byte_3 & 0b1100'0000) != 0b1000'0000) + ) + { + return {length, ErrorCode::TOO_SHORT}; + } + } + + const auto code_point = static_cast( + (leading_byte & 0b0000'0111) << 18 | + (next_byte_1 & 0b0011'1111) << 12 | + (next_byte_2 & 0b0011'1111) << 6 | + (next_byte_3 & 0b0011'1111) + ); + + if constexpr (not Correct) + { + if (code_point <= 0xffff) + { + return {length, ErrorCode::OVERLONG}; + } + + if (code_point > 0x10'ffff) + { + return {length, ErrorCode::TOO_LARGE}; + } + } + + + if constexpr (OutputType == CharsType::UTF32) + { + *(output + 0) = common::to_char(code_point); + + output += 1; + } + else + { + const auto [high_surrogate, low_surrogate] = [cp = code_point - 0x1'0000]() noexcept -> auto + { + const auto high = static_cast(0xd800 + (cp >> 10)); + const auto low = static_cast(0xdc00 + (cp & 0x3ff)); + + return std::make_pair(high, low); + }(); + + *(output + 0) = common::to_char(high_surrogate); + *(output + 1) = common::to_char(low_surrogate); + + output += 2; + } + + return {length, ErrorCode::NONE}; + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto invalid_input( + const typename input_type_of::const_pointer current + ) noexcept -> std::pair + { + const auto value = static_cast(*(current + 0)); + + // we either have too many continuation bytes or an invalid leading byte + constexpr std::ptrdiff_t length = 0; + + if ((value & 0b1100'0000) == 0b1000'0000) + { + // we have too many continuation bytes + return {length, ErrorCode::TOO_LONG}; + } + + // we have an invalid leading byte + return {length, ErrorCode::HEADER_BITS}; + } + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto code_points(const input_type_of input) noexcept -> std::size_t + { + return std::ranges::count_if( + input, + [](const auto byte) noexcept -> bool + { + const auto b = static_cast(byte); + + // -65 is 0b10111111, anything larger in two-complement's should start a new code point. + return b > -65; + } + ); + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto validate( + const typename input_type_of::const_pointer current, + const typename input_type_of::const_pointer end + ) noexcept -> std::pair + { + // 1-byte UTF-8 + // 2-bytes UTF-8 + // 3-bytes UTF-8 + // 4-bytes UTF-8 + + const auto leading_byte = static_cast(*(current + 0)); + + if (detail::check_byte_1(current)) + { + // we have a one-byte UTF-8 + constexpr std::ptrdiff_t length = 1; + + return {length, ErrorCode::NONE}; + } + + if (detail::check_byte_2(current)) + { + // we have a two-bytes UTF-8 + constexpr std::ptrdiff_t length = 2; + + // minimal bound checking + if (current + 1 >= end) + { + return {length, ErrorCode::TOO_SHORT}; + } + + const auto next_byte = static_cast(*(current + 1)); + + if ((next_byte & 0b1100'0000) != 0b1000'0000) + { + return {length, ErrorCode::TOO_SHORT}; + } + + // range check + const auto code_point = static_cast( + (leading_byte & 0b0001'1111) << 6 | + (next_byte & 0b0011'1111) + ); + + if (code_point < 0x80) + { + return {length, ErrorCode::OVERLONG}; + } + if (code_point > 0x7ff) + { + return {length, ErrorCode::TOO_LARGE}; + } + + return {length, ErrorCode::NONE}; + } + + if (detail::check_byte_3(current)) + { + // we have a three-byte UTF-8 + constexpr std::ptrdiff_t length = 3; + + // minimal bound checking + if (current + 2 >= end) + { + return {length, ErrorCode::TOO_SHORT}; + } + + const auto next_byte_1 = static_cast(*(current + 1)); + const auto next_byte_2 = static_cast(*(current + 2)); + + if ( + ((next_byte_1 & 0b1100'0000) != 0b1000'0000) or + ((next_byte_2 & 0b1100'0000) != 0b1000'0000) + ) + { + return {length, ErrorCode::TOO_SHORT}; + } + + // range check + const auto code_point = static_cast( + (leading_byte & 0b0000'1111) << 12 | + (next_byte_1 & 0b0011'1111) << 6 | + (next_byte_2 & 0b0011'1111) + ); + + if (code_point < 0x800) + { + return {length, ErrorCode::OVERLONG}; + } + if (code_point > 0xffff) + { + return {length, ErrorCode::TOO_LARGE}; + } + if (code_point > 0xd7ff and code_point < 0xe000) + { + return {length, ErrorCode::SURROGATE}; + } + + return {length, ErrorCode::NONE}; + } + + if (detail::check_byte_4(current)) + { + // we have a four-byte UTF-8 word + constexpr std::ptrdiff_t length = 4; + + // minimal bound checking + if (current + 3 >= end) + { + return {length, ErrorCode::TOO_SHORT}; + } + + const auto next_byte_1 = static_cast(*(current + 1)); + const auto next_byte_2 = static_cast(*(current + 2)); + const auto next_byte_3 = static_cast(*(current + 3)); + + if ( + ((next_byte_1 & 0b1100'0000) != 0b1000'0000) or + ((next_byte_2 & 0b1100'0000) != 0b1000'0000) or + ((next_byte_3 & 0b1100'0000) != 0b1000'0000) + ) + { + return {length, ErrorCode::TOO_SHORT}; + } + + // range check + const auto code_point = static_cast( + (leading_byte & 0b0000'0111) << 18 | + (next_byte_1 & 0b0011'1111) << 12 | + (next_byte_2 & 0b0011'1111) << 6 | + (next_byte_3 & 0b0011'1111) + ); + + if (code_point <= 0xffff) + { + return {length, ErrorCode::OVERLONG}; + } + if (code_point > 0x10'ffff) + { + return {length, ErrorCode::TOO_LARGE}; + } + + return {length, ErrorCode::NONE}; + } + + return detail::invalid_input(current); + } + + // 1-byte UTF-8 => 1 LATIN + // 2-bytes UTF-8 => 1 LATIN + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) and + ( + OutputType == CharsType::LATIN + ) + [[nodiscard]] constexpr auto write_latin( + typename output_type_of::pointer& output, + const typename input_type_of::const_pointer current, + const typename input_type_of::const_pointer end + ) noexcept -> std::pair + { + if constexpr (Pure) + { + return detail::write_byte_1(output, current, end); + } + else + { + if (detail::check_byte_1(current)) + { + return detail::write_byte_1(output, current, end); + } + + if (detail::check_byte_2(current)) + { + return detail::write_byte_2(output, current, end); + } + + if (detail::check_byte_3(current)) + { + return {3, ErrorCode::TOO_LARGE}; + } + + if (detail::check_byte_4(current)) + { + return {4, ErrorCode::TOO_LARGE}; + } + + return detail::invalid_input(current); + } + } + + // 1-byte UTF-8 => 1 UTF-16 + // 2-bytes UTF-8 => 1 UTF-16 + // 3-bytes UTF-8 => 1 UTF-16 + // 4-bytes UTF-8 => 2 UTF-16 + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) and + ( + OutputType == CharsType::UTF16_LE or + OutputType == CharsType::UTF16_BE + ) + [[nodiscard]] constexpr auto write_utf16( + typename output_type_of::pointer& output, + const typename input_type_of::const_pointer current, + const typename input_type_of::const_pointer end + ) noexcept -> std::pair + { + if constexpr (Pure) + { + return detail::write_byte_1(output, current, end); + } + else + { + if (detail::check_byte_1(current)) + { + return detail::write_byte_1(output, current, end); + } + + if (detail::check_byte_2(current)) + { + return detail::write_byte_2(output, current, end); + } + + if (detail::check_byte_3(current)) + { + return detail::write_byte_3(output, current, end); + } + + if (detail::check_byte_4(current)) + { + return detail::write_byte_4(output, current, end); + } + + return detail::invalid_input(current); + } + } + + // 1-byte UTF-8 => 1 UTF-32 + // 2-bytes UTF-8 => 1 UTF-32 + // 3-bytes UTF-8 => 1 UTF-32 + // 4-bytes UTF-8 => 2 UTF-32 + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) and + ( + OutputType == CharsType::UTF32 + ) + [[nodiscard]] constexpr auto write_utf32( + typename output_type_of::pointer& output, + const typename input_type_of::const_pointer current, + const typename input_type_of::const_pointer end + ) noexcept -> std::pair + { + if constexpr (Pure) + { + return detail::write_byte_1(output, current, end); + } + else + { + if (detail::check_byte_1(current)) + { + return detail::write_byte_1(output, current, end); + } + + if (detail::check_byte_2(current)) + { + return detail::write_byte_2(output, current, end); + } + + if (detail::check_byte_3(current)) + { + return detail::write_byte_3(output, current, end); + } + + if (detail::check_byte_4(current)) + { + return detail::write_byte_4(output, current, end); + } + + return detail::invalid_input(current); + } + } + + // UTF8_CHAR => UTF8 + // UTF8 => UTF8_CHAR + template + requires ( + InputType == CharsType::UTF8_CHAR and + OutputType == CharsType::UTF8 + ) or + ( + InputType == CharsType::UTF8 and + OutputType == CharsType::UTF8_CHAR + ) + [[nodiscard]] constexpr auto transform( + typename output_type_of::pointer& output, + const typename input_type_of::const_pointer current, + const typename input_type_of::const_pointer end + ) noexcept -> std::pair + { + if constexpr (Pure) + { + std::ranges::transform( + current, + current + 1, + output, + [](const auto c) noexcept -> auto + { + return common::to_char(c); + } + ); + return {1, ErrorCode::NONE}; + } + else + { + if (detail::check_byte_1(current)) + { + std::ranges::transform( + current, + current + 1, + output, + [](const auto c) noexcept -> auto + { + return common::to_char(c); + } + ); + return {1, ErrorCode::NONE}; + } + + if (detail::check_byte_2(current)) + { + if constexpr (not Correct) + { + // minimal bound checking + if (current + 1 >= end) + { + return {2, ErrorCode::TOO_SHORT}; + } + } + + std::ranges::transform( + current, + current + 2, + output, + [](const auto c) noexcept -> auto + { + return common::to_char(c); + } + ); + return {2, ErrorCode::NONE}; + } + + if (detail::check_byte_3(current)) + { + if constexpr (not Correct) + { + // minimal bound checking + if (current + 2 >= end) + { + return {3, ErrorCode::TOO_SHORT}; + } + } + + std::ranges::transform( + current, + current + 3, + output, + [](const auto c) noexcept -> auto + { + return common::to_char(c); + } + ); + return {3, ErrorCode::NONE}; + } + + if (detail::check_byte_4(current)) + { + if constexpr (not Correct) + { + // minimal bound checking + if (current + 3 >= end) + { + return {4, ErrorCode::TOO_SHORT}; + } + } + + std::ranges::transform( + current, + current + 4, + output, + [](const auto c) noexcept -> auto + { + return common::to_char(c); + } + ); + return {4, ErrorCode::NONE}; + } + + return detail::invalid_input(current); + } + } + + namespace scalar + { + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto advance_of() noexcept -> std::ptrdiff_t + { + std::ignore = OutputType; + + using char_type = typename input_type_of::value_type; + + return sizeof(data_type) / sizeof(char_type); + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto read(const typename input_type_of::const_pointer source) noexcept -> data_type + { + std::ignore = OutputType; + + return memory::unaligned_load(source); + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto validate(const input_type_of input) noexcept -> result_error_input_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + + using input_type = input_type_of; + using pointer_type = typename input_type::const_pointer; + using size_type = typename input_type::size_type; + + constexpr auto advance = scalar::advance_of(); + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const auto check = [&](const size_type n) noexcept -> result_error_input_type + { + const auto end = it_input_current + n; + while (it_input_current < end) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + + const auto [length, error] = ::utf8::validate(it_input_current, it_input_end); + if (error != ErrorCode::NONE) + { + return {.error = error, .input = current_input_length}; + } + + it_input_current += length; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current >= end); + const auto current_input_length = static_cast(it_input_current - it_input_begin); + return {.error = ErrorCode::NONE, .input = current_input_length}; + }; + + while (it_input_current + advance <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance}; + #endif + + const auto data = scalar::read(it_input_current); + + if (const auto sign = common::sign_of(data); + not sign.pure()) + { + const auto start_count = sign.start_count(); + const auto end_count = sign.end_count(); + const auto unknown_count = advance - start_count - end_count; + + it_input_current += start_count; + if (const auto result = check(unknown_count); + result.has_error()) + { + return result; + } + } + else + { + it_input_current += advance; + } + } + + const auto remaining = static_cast(it_input_end - it_input_current); + GAL_PROMETHEUS_ERROR_ASSUME(remaining < advance); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, remaining}; + #endif + + if (const auto result = check(remaining); + result.has_error()) + { + return result; + } + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + return {.error = ErrorCode::NONE, .input = current_input_length}; + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto rewind_and_validate( + const typename input_type_of::const_pointer begin, + const typename input_type_of::const_pointer current, + const typename input_type_of::const_pointer end + ) noexcept -> result_error_input_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(begin != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(end >= current); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current >= begin); + + using input_type = input_type_of; + using pointer_type = typename input_type::const_pointer; + using size_type = typename input_type::size_type; + + // First check that we start with a leading byte + if ((begin[0] & 0b1100'0000) == 0b1000'0000) + { + return {.error = ErrorCode::TOO_LONG, .input = 0}; + } + + std::size_t extra_count = 0; + // A leading byte cannot be further than 4 bytes away + for (std::ptrdiff_t i = 0; i < 5; ++i) + { + if (const auto byte = static_cast(current[-i]); + (byte & 0b1100'0000) == 0b1000'0000) + { + break; + } + extra_count += 1; + } + + const pointer_type it_current = current - extra_count; + const auto length = static_cast(end - begin + extra_count); + + auto result = scalar::validate({it_current, length}); + result.input -= extra_count; + return result; + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto length( + const input_type_of input + ) noexcept -> typename input_type_of::size_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + + using size_type = typename input_type_of::size_type; + + // ReSharper disable CppClangTidyBugproneBranchClone + if constexpr ( + OutputType == CharsType::LATIN + ) + { + return utf8::code_points(input); + } + // ReSharper restore CppClangTidyBugproneBranchClone + else if constexpr ( + OutputType == CharsType::UTF16_LE or + OutputType == CharsType::UTF16_BE or + OutputType == CharsType::UTF16 + ) + { + return std::ranges::fold_left( + input.begin(), + input.end(), + size_type{0}, + [](const size_type total, const auto byte) noexcept -> size_type + { + // -65 is 0b10111111 + return + total + + (static_cast(byte) > -65) // + + + (static_cast(byte) >= 240) // + ; + } + ); + } + // ReSharper disable CppClangTidyBugproneBranchClone + else if constexpr ( + OutputType == CharsType::UTF32 + ) + { + return utf8::code_points(input); + } + // ReSharper restore CppClangTidyBugproneBranchClone + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) and + ( + OutputType == CharsType::LATIN + ) + [[nodiscard]] constexpr auto write_latin( + const typename output_type_of::pointer output, + const input_type_of input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using input_type = input_type_of; + using pointer_type = typename input_type::const_pointer; + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + const auto advance = scalar::advance_of(); + const auto transform = [&](const decltype(advance) n) noexcept -> result_error_input_output_type + { + const auto end = it_input_current + n; + while (it_input_current < end) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + const auto [length, error] = ::utf8::write_latin(it_output_current, it_input_current, it_input_end); + if (error != ErrorCode::NONE) + { + return {.error = error, .input = current_input_length, .output = current_output_length}; + } + + it_input_current += length; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current >= end); + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + }; + + while (it_input_current + advance <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance}; + #endif + + const auto data = scalar::read(it_input_current); + + if constexpr (Pure) + { + const auto result = transform.template operator()(advance); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not result.has_error()); + } + else + { + if (const auto sign = common::sign_of(data); + not sign.pure()) + { + const auto start_count = sign.start_count(); + const auto end_count = sign.end_count(); + const auto unknown_count = advance - start_count - end_count; + + const auto result1 = transform.template operator()(static_cast(start_count)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not result1.has_error()); + + if (const auto result2 = transform.template operator()(static_cast(unknown_count)); + result2.has_error()) + { + return result2; + } + } + else + { + const auto result = transform.template operator()(advance); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not result.has_error()); + } + } + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(remaining < advance); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + if (const auto result = transform.template operator()(remaining); + result.has_error()) + { + return result; + } + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) and + ( + OutputType == CharsType::UTF16_LE or + OutputType == CharsType::UTF16_BE + ) + [[nodiscard]] constexpr auto write_utf16( + const typename output_type_of::pointer output, + const input_type_of input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using input_type = input_type_of; + using pointer_type = typename input_type::const_pointer; + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + const auto advance = scalar::advance_of(); + const auto transform = [&](const decltype(advance) n) noexcept -> result_error_input_output_type + { + const auto end = it_input_current + n; + while (it_input_current < end) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + const auto [length, error] = ::utf8::write_utf16(it_output_current, it_input_current, it_input_end); + if (error != ErrorCode::NONE) + { + return {.error = error, .input = current_input_length, .output = current_output_length}; + } + + it_input_current += length; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current >= end); + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + }; + + while (it_input_current + advance <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance}; + #endif + + const auto data = scalar::read(it_input_current); + + if constexpr (Pure) + { + const auto result = transform.template operator()(advance); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not result.has_error()); + } + else + { + if (const auto sign = common::sign_of(data); + not sign.pure()) + { + const auto start_count = sign.start_count(); + const auto end_count = sign.end_count(); + const auto unknown_count = advance - start_count - end_count; + + const auto result1 = transform.template operator()(static_cast(start_count)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not result1.has_error()); + + if (const auto result2 = transform.template operator()(static_cast(unknown_count)); + result2.has_error()) + { + return result2; + } + } + else + { + const auto result = transform.template operator()(advance); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not result.has_error()); + } + } + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(remaining < advance); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + if (const auto result = transform.template operator()(remaining); + result.has_error()) + { + return result; + } + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) and + ( + OutputType == CharsType::UTF32 + ) + [[nodiscard]] constexpr auto write_utf32( + const typename output_type_of::pointer output, + const input_type_of input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using input_type = input_type_of; + using pointer_type = typename input_type::const_pointer; + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + const auto advance = scalar::advance_of(); + const auto transform = [&](const decltype(advance) n) noexcept -> result_error_input_output_type + { + const auto end = it_input_current + n; + while (it_input_current < end) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + const auto [length, error] = ::utf8::write_utf32(it_output_current, it_input_current, it_input_end); + if (error != ErrorCode::NONE) + { + return {.error = error, .input = current_input_length, .output = current_output_length}; + } + + it_input_current += length; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current >= end); + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + }; + + while (it_input_current + advance <= it_input_end) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + advance}; + #endif + + const auto data = scalar::read(it_input_current); + + if constexpr (Pure) + { + const auto result = transform.template operator()(advance); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not result.has_error()); + } + else + { + if (const auto sign = common::sign_of(data); + not sign.pure()) + { + const auto start_count = sign.start_count(); + const auto end_count = sign.end_count(); + const auto unknown_count = advance - start_count - end_count; + + const auto result1 = transform.template operator()(static_cast(start_count)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not result1.has_error()); + + if (const auto result2 = transform.template operator()(static_cast(unknown_count)); + result2.has_error()) + { + return result2; + } + } + else + { + const auto result = transform.template operator()(advance); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not result.has_error()); + } + } + } + + const auto remaining = it_input_end - it_input_current; + GAL_PROMETHEUS_ERROR_ASSUME(remaining < advance); + + if (remaining != 0) + { + #if GAL_PROMETHEUS_COMPILER_DEBUG + [[maybe_unused]] const auto debug_input_data = std::span{it_input_current, it_input_current + remaining}; + #endif + + if (const auto result = transform.template operator()(remaining); + result.has_error()) + { + return result; + } + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + template + requires ( + InputType == CharsType::UTF8_CHAR or + InputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto rewind_and_convert( + typename output_type_of::pointer output, + const typename input_type_of::const_pointer furthest_possible_begin, + const input_type_of input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(furthest_possible_begin != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() >= furthest_possible_begin); + // fixme + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(furthest_possible_begin - input.data() <= 3); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using input_type = input_type_of; + using pointer_type = typename input_type::const_pointer; + // using size_type = typename input_type::size_type; + + // using output_pointer_type = typename output_type::pointer; + // using output_char_type = typename output_type::value_type; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + // pointer_type it_input_current = it_input_begin; + // const pointer_type it_input_end = it_input_begin + input_length; + + // const output_pointer_type it_output_begin = output; + // output_pointer_type it_output_current = it_output_begin; + + // const auto range = std::ranges::subrange{std::make_reverse_iterator(it_input_current), std::make_reverse_iterator(furthest_possible_begin)}; + const auto range = std::ranges::subrange{furthest_possible_begin, it_input_begin} | std::views::reverse; + // fixme: no leading bytes? + const auto extra_count = std::ranges::distance( + range | + std::views::take_while( + [](const auto c) noexcept -> bool + { + return (c & 0b1100'0000) != 0b1000'0000; + } + ) + ); + + const auto it_current = it_input_begin - extra_count; + + auto result = Scalar::convert(output, {it_current, input_length + extra_count}); + if (result.has_error()) + { + result.input -= extra_count; + } + + return result; + } + + // UTF8_CHAR => UTF8 + // UTF8 => UTF8_CHAR + template + requires ( + InputType == CharsType::UTF8_CHAR and + OutputType == CharsType::UTF8 + ) or + ( + InputType == CharsType::UTF8 and + OutputType == CharsType::UTF8_CHAR + ) + [[nodiscard]] constexpr auto transform( + const typename output_type_of::pointer output, + const input_type_of input + ) noexcept -> result_error_input_type + { + using char_type = typename input_type_of::value_type; + + if (const auto result = scalar::validate(input); + result.has_error()) + { + std::memcpy(output, input.data(), result.input * sizeof(char_type)); + return {.error = result.error, .input = result.input}; + } + + std::memcpy(output, input.data(), input.size() * sizeof(char_type)); + return {.error = ErrorCode::NONE, .input = input.size()}; + } + } + } + + namespace utf16 + { + using input_type = chars::utf16::input_type; + // using char_type = chars::utf16::char_type; + using size_type = chars::utf16::size_type; + using pointer_type = chars::utf16::pointer_type; + + template + requires ( + InputType == CharsType::UTF16_LE or + InputType == CharsType::UTF16_BE + ) + [[nodiscard]] constexpr auto validate(const pointer_type current, const pointer_type end) noexcept -> std::pair + { + // 1-word UTF-16 + // 2-words UTF-16(surrogate pair) + + if (const auto leading_word = common::to_native_utf16(*(current + 0)); + (leading_word & 0xf800) == 0xd800) + { + // we have a two-word UTF16 + // must be a surrogate pair + constexpr std::ptrdiff_t length = 2; + + // minimal bound checking + if (current + 1 >= end) + { + return {length, ErrorCode::SURROGATE}; + } + + if (const auto diff = static_cast(leading_word - 0xd800); + diff > 0x3ff) + { + return {length, ErrorCode::SURROGATE}; + } + + const auto next_word = common::to_native_utf16(*(current + 1)); + if (const auto diff = static_cast(next_word - 0xdc00); + diff > 0x3ff) + { + return {length, ErrorCode::SURROGATE}; + } + + return {length, ErrorCode::NONE}; + } + + // we have a one-word UTF16 + constexpr std::ptrdiff_t length = 1; + + return {length, ErrorCode::NONE}; + } + + // 1 UTF-16 => 1 LATIN + template + requires ( + InputType == CharsType::UTF16_LE or + InputType == CharsType::UTF16_BE + ) and + ( + OutputType == CharsType::LATIN + ) + [[nodiscard]] constexpr auto write_latin( + typename output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + std::ignore = end; + + constexpr std::ptrdiff_t length = 1; + + const auto value = common::to_native_utf16(*(current + 0)); + + if constexpr (not Pure or not Correct) + { + if ((value & 0xff00) != 0) + { + return {length, ErrorCode::TOO_LARGE}; + } + } + + *(output + 0) = common::to_char(value); + + output += 1; + return {length, ErrorCode::NONE}; + } + + // 1 UTF-16 => 1/2/3 UTF-8 + // 2 UTF-16(surrogate pair) => 4 UTF-8 + template + requires ( + InputType == CharsType::UTF16_LE or + InputType == CharsType::UTF16_BE + ) and + ( + OutputType == CharsType::UTF8_CHAR or + OutputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto write_utf8( + typename output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + if constexpr (const auto leading_word = common::to_native_utf16(*(current + 0)); + Pure) + { + constexpr std::ptrdiff_t length = 1; + + *(output + 0) = common::to_char(leading_word); + + output += 1; + return {length, ErrorCode::NONE}; + } + else + { + if ((leading_word & 0xff80) == 0) + { + // 1 utf16 => 1 utf8 + constexpr std::size_t length = 1; + + *(output + 0) = common::to_char(leading_word); + + output += 1; + return {length, ErrorCode::NONE}; + } + + if ((leading_word & 0xf800) == 0) + { + // 1 utf16 => 2 utf8 + constexpr std::size_t length = 1; + + // 0b110?'???? 0b10??'???? + const auto c1 = static_cast((leading_word >> 6) | 0b1100'0000); + const auto c2 = static_cast((leading_word & 0b0011'1111) | 0b1000'0000); + + *(output + 0) = common::to_char(c1); + *(output + 1) = common::to_char(c2); + + output += 2; + return {length, ErrorCode::NONE}; + } + + if ((leading_word & 0xf800) != 0xd800) + { + // 1 utf16 => 3 utf8 + constexpr std::size_t length = 1; + + // 0b1110'???? 0b10??'???? 0b10??'???? + const auto c1 = static_cast((leading_word >> 12) | 0b1110'0000); + const auto c2 = static_cast(((leading_word >> 6) & 0b0011'1111) | 0b1000'0000); + const auto c3 = static_cast((leading_word & 0b0011'1111) | 0b1000'0000); + + *(output + 0) = common::to_char(c1); + *(output + 1) = common::to_char(c2); + *(output + 2) = common::to_char(c3); + + output += 3; + return {length, ErrorCode::NONE}; + } + + // 2 utf16 => 4 utf8 + // must be a surrogate pair + constexpr std::size_t length = 2; + + // minimal bound checking + if (current + 1 >= end) + { + return {length, ErrorCode::SURROGATE}; + } + + const auto diff = static_cast(leading_word - 0xd800); + + if constexpr (not Correct) + { + if (diff > 0x3ff) + { + return {length, ErrorCode::SURROGATE}; + } + } + + const auto next_word = common::to_native_utf16(*(current + 1)); + const auto next_diff = static_cast(next_word - 0xdc00); + + if constexpr (not Correct) + { + if (next_diff > 0x3ff) + { + return {length, ErrorCode::SURROGATE}; + } + } + + const auto value = static_cast((diff << 10) + next_diff + 0x1'0000); + + // 0b1111'0??? 0b10??'???? 0b10??'???? 0b10??'???? + const auto c1 = static_cast((value >> 18) | 0b1111'0000); + const auto c2 = static_cast(((value >> 12) & 0b0011'1111) | 0b1000'0000); + const auto c3 = static_cast(((value >> 6) & 0b0011'1111) | 0b1000'0000); + const auto c4 = static_cast((value & 0b0011'1111) | 0b1000'0000); + + *(output + 0) = common::to_char(c1); + *(output + 1) = common::to_char(c2); + *(output + 2) = common::to_char(c3); + *(output + 3) = common::to_char(c4); + + output += 4; + return {length, ErrorCode::NONE}; + } + } + + // 1 UTF-16 => 1 UTF-32 + // 2 UTF-16(surrogate pair) => 1 UTF-32 + template + requires ( + InputType == CharsType::UTF16_LE or + InputType == CharsType::UTF16_BE + ) and + ( + OutputType == CharsType::UTF32 + ) + [[nodiscard]] constexpr auto write_utf32( + typename output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + if constexpr (const auto leading_word = common::to_native_utf16(*(current + 0)); + Pure) + { + constexpr std::ptrdiff_t length = 1; + + *(output + 0) = common::to_char(leading_word); + + output += 1; + return {length, ErrorCode::NONE}; + } + else + { + if ((leading_word & 0xf800) == 0xd800) + { + // we have a two-word UTF16 + // must be a surrogate pair + constexpr std::ptrdiff_t length = 2; + + // minimal bound checking + if (current + 1 >= end) + { + return {length, ErrorCode::SURROGATE}; + } + + const auto diff = static_cast(leading_word - 0xd800); + + if constexpr (not Correct) + { + if (diff > 0x3ff) + { + return {length, ErrorCode::SURROGATE}; + } + } + + const auto next_word = common::to_native_utf16(*(current + 1)); + const auto next_diff = static_cast(next_word - 0xdc00); + + if constexpr (not Correct) + { + if (next_diff > 0x3ff) + { + return {length, ErrorCode::SURROGATE}; + } + } + + const auto value = static_cast((diff << 10) + next_diff + 0x1'0000); + + *(output + 0) = common::to_char(value); + + output += 1; + return {length, ErrorCode::NONE}; + } + + // we have a one-word UTF16 + constexpr std::ptrdiff_t length = 1; + + *(output + 0) = common::to_char(leading_word); + + output += 1; + return {length, ErrorCode::NONE}; + } + } + + namespace scalar + { + template + requires ( + InputType == CharsType::UTF16_LE or + InputType == CharsType::UTF16_BE + ) + [[nodiscard]] constexpr auto validate(const input_type input) noexcept -> result_error_input_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + while (it_input_current < it_input_end) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + + const auto [length, error] = ::utf16::validate(it_input_current, it_input_end); + GAL_PROMETHEUS_ERROR_ASSUME(length == 1 or length == 2); + + if (error != ErrorCode::NONE) + { + return {.error = error, .input = current_input_length}; + } + + it_input_current += length; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + return {.error = ErrorCode::NONE, .input = current_input_length}; + } + + template + requires ( + InputType == CharsType::UTF16_LE or + InputType == CharsType::UTF16_BE + ) + [[nodiscard]] constexpr auto length(const input_type input) noexcept -> size_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + + // ReSharper disable CppClangTidyBugproneBranchClone + if constexpr ( + OutputType == CharsType::LATIN + ) + { + return input.size(); + } + // ReSharper restore CppClangTidyBugproneBranchClone + else if constexpr ( + OutputType == CharsType::UTF8_CHAR or + OutputType == CharsType::UTF8 + ) + { + return std::ranges::fold_left( + input.begin(), + input.end(), + size_type{0}, + [](const size_type total, const auto word) noexcept -> size_type + { + const auto native_word = common::to_native_utf16(word); + + return + total + + // ASCII + 1 + + + // non-ASCII is at least 2 bytes, surrogates are 2*2 == 4 bytes + (native_word > 0x7f) + + + (native_word > 0x7ff && native_word <= 0xd7ff) + + + (native_word >= 0xe000); + } + ); + } + // ReSharper disable CppClangTidyBugproneBranchClone + else if constexpr ( + OutputType == CharsType::UTF16_LE or + OutputType == CharsType::UTF16_BE or + OutputType == CharsType::UTF16 + ) + { + return input.size(); + } + // ReSharper restore CppClangTidyBugproneBranchClone + else if constexpr ( + OutputType == CharsType::UTF32 + ) + { + return std::ranges::fold_left( + input.begin(), + input.end(), + size_type{0}, + [](const size_type total, const auto word) noexcept -> size_type + { + const auto native_word = common::to_native_utf16(word); + + return total + ((native_word & 0xfc00) != 0xdc00); + } + ); + } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template + requires ( + InputType == CharsType::UTF16_LE or + InputType == CharsType::UTF16_BE + ) and + ( + OutputType == CharsType::LATIN + ) + [[nodiscard]] constexpr auto write_latin( + const typename output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + while (it_input_current < it_input_end) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + const auto [length, error] = ::utf16::write_latin(it_output_current, it_input_current, it_input_end); + GAL_PROMETHEUS_ERROR_ASSUME(length == 1); + + if (error != ErrorCode::NONE) + { + return {.error = error, .input = current_input_length, .output = current_output_length}; + } + + it_input_current += length; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + template + requires ( + InputType == CharsType::UTF16_LE or + InputType == CharsType::UTF16_BE + ) and + ( + OutputType == CharsType::UTF8_CHAR or + OutputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto write_utf8( + const typename output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + while (it_input_current < it_input_end) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + const auto [length, error] = ::utf16::write_utf8(it_output_current, it_input_current, it_input_end); + + if (error != ErrorCode::NONE) + { + return {.error = error, .input = current_input_length, .output = current_output_length}; + } + + it_input_current += length; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + template + requires ( + InputType == CharsType::UTF16_LE or + InputType == CharsType::UTF16_BE + ) and + ( + OutputType == CharsType::UTF32 + ) + [[nodiscard]] constexpr auto write_utf32( + const typename output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + while (it_input_current < it_input_end) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + const auto [length, error] = ::utf16::write_utf32(it_output_current, it_input_current, it_input_end); + GAL_PROMETHEUS_ERROR_ASSUME(length == 1 or length == 2); + + if (error != ErrorCode::NONE) + { + return {.error = error, .input = current_input_length, .output = current_output_length}; + } + + it_input_current += length; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + constexpr auto flip( + const output_type_of::pointer output, + const input_type_of input + ) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + std::ranges::transform( + input, + output, + [](const auto word) noexcept + { + return std::byteswap(word); + } + ); + } + + template + requires ( + InputType == CharsType::UTF16_LE and + OutputType == CharsType::UTF16_BE + ) or + ( + InputType == CharsType::UTF16_BE and + OutputType == CharsType::UTF16_LE + ) + constexpr auto transform( + const typename output_type_of::pointer output, + const input_type_of input + ) noexcept -> result_error_input_type + { + if (const auto result = scalar::validate(input); + result.has_error()) + { + scalar::flip(output, {input.data(), result.input}); + return {.error = result.error, .input = result.input}; + } + + scalar::flip(output, input); + return {.error = ErrorCode::NONE, .input = input.size()}; + } + } + } + + namespace utf32 + { + using input_type = chars::utf32::input_type; + // using char_type = chars::utf32::char_type; + using size_type = chars::utf32::size_type; + using pointer_type = chars::utf32::pointer_type; + + [[nodiscard]] constexpr auto validate(const pointer_type current, const pointer_type end) noexcept -> std::pair + { + std::ignore = end; + constexpr std::ptrdiff_t length = 1; + + const auto value = static_cast(*(current + 0)); + + if (value > 0x10'ffff) + { + return {length, ErrorCode::TOO_LARGE}; + } + + if (value >= 0xd800 and value <= 0xdfff) + { + return {length, ErrorCode::SURROGATE}; + } + + return {length, ErrorCode::NONE}; + } + + // 1 UTF-32 => 1 LATIN + template + requires ( + OutputType == CharsType::LATIN + ) + [[nodiscard]] constexpr auto write_latin( + typename output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + std::ignore = end; + + constexpr std::ptrdiff_t length = 1; + + const auto value = static_cast(*(current + 0)); + + if constexpr (not Pure or not Correct) + { + if ((value & 0xffff'ff00) != 0) + { + return {length, ErrorCode::TOO_LARGE}; + } + } + + *(output + 0) = common::to_char(value); + + output += 1; + return {length, ErrorCode::NONE}; + } + + // 1 UTF-32 => 1/2/3/4 UTF-8 + template + requires ( + OutputType == CharsType::UTF8_CHAR or + OutputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto write_utf8( + typename output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + std::ignore = end; + + constexpr std::ptrdiff_t length = 1; + + if constexpr (const auto value = static_cast(*(current + 0)); + Pure) + { + *(output + 0) = common::to_char(value); + + output += 1; + return {length, ErrorCode::NONE}; + } + else + { + if ((value & 0xffff'ff80) == 0) + { + // 1-byte utf8 + + *(output + 0) = common::to_char(value); + + output += 1; + return {length, ErrorCode::NONE}; + } + + if ((value & 0xffff'f800) == 0) + { + // 2-bytes utf8 + + // 0b110?'???? 0b10??'???? + const auto c1 = static_cast((value >> 6) | 0b1100'0000); + const auto c2 = static_cast((value & 0b0011'1111) | 0b1000'0000); + + *(output + 0) = common::to_char(c1); + *(output + 1) = common::to_char(c2); + + output += 2; + return {length, ErrorCode::NONE}; + } + + if ((value & 0xffff'0000) == 0) + { + // 3-bytes utf8 + + if constexpr (not Correct) + { + if (value >= 0xd800 and value <= 0xdfff) + { + return {length, ErrorCode::SURROGATE}; + } + } + + // 0b1110'???? 0b10??'???? 0b10??'???? + const auto c1 = static_cast((value >> 12) | 0b1110'0000); + const auto c2 = static_cast(((value >> 6) & 0b0011'1111) | 0b1000'0000); + const auto c3 = static_cast((value & 0b0011'1111) | 0b1000'0000); + + *(output + 0) = common::to_char(c1); + *(output + 1) = common::to_char(c2); + *(output + 2) = common::to_char(c3); + + output += 3; + return {length, ErrorCode::NONE}; + } + + // 4-bytes utf8 + + if constexpr (not Correct) + { + if (value > 0x0010'ffff) + { + return {length, ErrorCode::TOO_LARGE}; + } + } + + // 0b1111'0??? 0b10??'???? 0b10??'???? 0b10??'???? + const auto c1 = static_cast((value >> 18) | 0b1111'0000); + const auto c2 = static_cast(((value >> 12) & 0b0011'1111) | 0b1000'0000); + const auto c3 = static_cast(((value >> 6) & 0b0011'1111) | 0b1000'0000); + const auto c4 = static_cast((value & 0b0011'1111) | 0b1000'0000); + + *(output + 0) = common::to_char(c1); + *(output + 1) = common::to_char(c2); + *(output + 2) = common::to_char(c3); + *(output + 3) = common::to_char(c4); + + output += 4; + return {length, ErrorCode::NONE}; + } + } + + // 1 UTF-32 => 1/2 UTF-16 + template + requires ( + OutputType == CharsType::UTF16_LE or + OutputType == CharsType::UTF16_BE + ) + [[nodiscard]] constexpr auto write_utf16( + typename output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + std::ignore = end; + + constexpr std::ptrdiff_t length = 1; + + if constexpr (const auto value = static_cast(*(current + 0)); + Pure) + { + *(output + 0) = common::to_char(value); + + output += 1; + return {length, ErrorCode::NONE}; + } + else + { + if ((value & 0xffff'0000) == 0) + { + if constexpr (not Correct) + { + if (value >= 0xd800 and value <= 0xdfff) + { + return {length, ErrorCode::SURROGATE}; + } + } + + *(output + 0) = common::to_char(value); + + output += 1; + return {length, ErrorCode::NONE}; + } + + // will generate a surrogate pair + + if constexpr (not Correct) + { + if (value > 0x0010'ffff) + { + return {length, ErrorCode::TOO_LARGE}; + } + } + + const auto [high_surrogate, low_surrogate] = [v = value - 0x0001'0000]() noexcept + { + const auto high = static_cast(0xd800 + (v >> 10)); + const auto low = static_cast(0xdc00 + (v & 0x3ff)); + + return std::make_pair(high, low); + }(); + + *(output + 0) = common::to_char(high_surrogate); + *(output + 1) = common::to_char(low_surrogate); + + output += 2; + return {length, ErrorCode::NONE}; + } + } + + namespace scalar + { + [[nodiscard]] constexpr auto validate(const input_type input) noexcept -> result_error_input_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + while (it_input_current < it_input_end) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + + const auto [length, error] = ::utf32::validate(it_input_current, it_input_end); + GAL_PROMETHEUS_ERROR_ASSUME(length == 1); + + if (error != ErrorCode::NONE) + { + return {.error = error, .input = current_input_length}; + } + + it_input_current += length; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + return {.error = ErrorCode::NONE, .input = current_input_length}; + } + + template + [[nodiscard]] constexpr auto length(const input_type input) noexcept -> size_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + + // ReSharper disable CppClangTidyBugproneBranchClone + if constexpr ( + OutputType == CharsType::LATIN + ) + { + return input.size(); + } + // ReSharper restore CppClangTidyBugproneBranchClone + else if constexpr ( + OutputType == CharsType::UTF8_CHAR or + OutputType == CharsType::UTF8 + ) + { + return std::ranges::fold_left( + input.begin(), + input.end(), + size_type{0}, + [](const size_type total, const auto data) noexcept -> size_type + { + const auto v = static_cast(data); + return + total + + 1 // ascii + + + (v > 0x7f) // two-byte + + + (v > 0x7ff) // three-byte + + + (v > 0xffff) // four-byte + ; + } + ); + } + else if constexpr ( + OutputType == CharsType::UTF16_LE or + OutputType == CharsType::UTF16_BE or + OutputType == CharsType::UTF16 + ) + { + return std::ranges::fold_left( + input.begin(), + input.end(), + size_type{0}, + [](const size_type total, const auto data) noexcept -> size_type + { + const auto v = static_cast(data); + return + total + + 1 // non-surrogate word + + + (v > 0xffff) // surrogate pair + ; + } + ); + } + // ReSharper disable CppClangTidyBugproneBranchClone + else if constexpr ( + OutputType == CharsType::UTF32 + ) + { + return input.size(); + } + // ReSharper restore CppClangTidyBugproneBranchClone + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template + requires ( + OutputType == CharsType::LATIN + ) + [[nodiscard]] constexpr auto write_latin( + const typename output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + while (it_input_current < it_input_end) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + const auto [length, error] = ::utf32::write_latin(it_output_current, it_input_current, it_input_end); + GAL_PROMETHEUS_ERROR_ASSUME(length == 1); + + if (error != ErrorCode::NONE) + { + return {.error = error, .input = current_input_length, .output = current_output_length}; + } + + it_input_current += length; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + template + requires ( + OutputType == CharsType::UTF8_CHAR or + OutputType == CharsType::UTF8 + ) + [[nodiscard]] constexpr auto write_utf8( + const typename output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + while (it_input_current < it_input_end) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + const auto [length, error] = ::utf32::write_utf8(it_output_current, it_input_current, it_input_end); + GAL_PROMETHEUS_ERROR_ASSUME(length == 1); + + if (error != ErrorCode::NONE) + { + return {.error = error, .input = current_input_length, .output = current_output_length}; + } + + it_input_current += length; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + + template + requires ( + OutputType == CharsType::UTF16_LE or + OutputType == CharsType::UTF16_BE + ) + [[nodiscard]] constexpr auto write_utf16( + const typename output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); + + using output_type = output_type_of; + using output_pointer_type = typename output_type::pointer; + + const auto input_length = input.size(); + + const pointer_type it_input_begin = input.data(); + pointer_type it_input_current = it_input_begin; + const pointer_type it_input_end = it_input_begin + input_length; + + const output_pointer_type it_output_begin = output; + output_pointer_type it_output_current = it_output_begin; + + while (it_input_current < it_input_end) + { + const auto current_input_length = static_cast(it_input_current - it_input_begin); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + + const auto [length, error] = ::utf32::write_utf16(it_output_current, it_input_current, it_input_end); + GAL_PROMETHEUS_ERROR_ASSUME(length == 1); + + if (error != ErrorCode::NONE) + { + return {.error = error, .input = current_input_length, .output = current_output_length}; + } + + it_input_current += length; + } + + // ================================================== + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_input_current == it_input_end); + const auto current_input_length = static_cast(input_length); + const auto current_output_length = static_cast(it_output_current - it_output_begin); + return {.error = ErrorCode::NONE, .input = current_input_length, .output = current_output_length}; + } + } + } +} + +namespace gal::prometheus::chars +{ + [[nodiscard]] auto width_of(const EncodingType type) noexcept -> std::size_t + { + switch (type) + { + case EncodingType::UNKNOWN: + { + return 0; + } + case EncodingType::UTF8: + { + return 3; + } + case EncodingType::UTF16_LE: + case EncodingType::UTF16_BE: + { + return 2; + } + case EncodingType::UTF32_LE: + case EncodingType::UTF32_BE: + { + return 4; + } + default: { GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); } // NOLINT(clang-diagnostic-covered-switch-default) + } + } + + [[nodiscard]] auto bom_of(const std::span string) noexcept -> EncodingType + { + // https://en.wikipedia.org/wiki/Byte_order_mark#Byte-order_marks_by_encoding + + const auto length = string.size(); + + if (length < 2) + { + return EncodingType::UNKNOWN; + } + + if (string[0] == 0xff and string[1] == 0xfe) + { + if (length >= 4 and string[2] == 0x00 and string[3] == 0x00) + { + return EncodingType::UTF32_LE; + } + return EncodingType::UTF16_LE; + } + + if (string[0] == 0xfe and string[1] == 0xff) + { + return EncodingType::UTF16_BE; + } + + if (length >= 4 and string[0] == 0x00 and string[1] == 0x00 and string[2] == 0xfe and string[3] == 0xff) + { + return EncodingType::UTF32_BE; + } + + if (length >= 3 and string[0] == 0xef and string[1] == 0xbb and string[2] == 0xbf) + { + return EncodingType::UTF8; + } + + return EncodingType::UNKNOWN; + } + + [[nodiscard]] auto bom_of(const std::span string) noexcept -> EncodingType + { + static_assert(sizeof(char) == sizeof(char8_t)); + + const auto* char8_string = GAL_PROMETHEUS_SEMANTIC_UNRESTRICTED_CHAR_POINTER_CAST(char8_t, string.data()); + return bom_of({char8_string, string.size()}); + } + + namespace latin + { + auto validate(const pointer_type current, const pointer_type end) noexcept -> std::pair + { + return ::latin::validate(current, end); + } + + [[nodiscard]] auto write_utf8( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::latin::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::latin::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::latin::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::latin::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::latin::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::latin::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::latin::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_le_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::latin::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_le_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::latin::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::latin::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_be_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::latin::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_be_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::latin::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf32( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::latin::write_utf32(output, current, end); + } + + [[nodiscard]] auto write_utf32_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::latin::write_utf32(output, current, end); + } + + [[nodiscard]] auto write_utf32_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::latin::write_utf32(output, current, end); + } + + namespace scalar + { + auto validate(const input_type input) noexcept -> result_error_input_type + { + return ::latin::scalar::validate(input); + } + + auto validate(const pointer_type input) noexcept -> result_error_input_type + { + return validate({input, std::char_traits::length(input)}); + } + + auto length_for_latin(const input_type input) noexcept -> size_type + { + return input.size(); + } + + auto length_for_latin(pointer_type input) noexcept -> size_type + { + return length_for_latin({input, std::char_traits::length(input)}); + } + + auto length_for_utf8(const input_type input) noexcept -> size_type + { + const auto length = ::latin::scalar::length(input); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length == ::latin::scalar::length(input)); + + return length; + } + + auto length_for_utf8(const pointer_type input) noexcept -> size_type + { + return length_for_utf8({input, std::char_traits::length(input)}); + } + + auto length_for_utf16(const input_type input) noexcept -> size_type + { + const auto length = ::latin::scalar::length(input); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length == ::latin::scalar::length(input)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length == ::latin::scalar::length(input)); + + return length; + } + + auto length_for_utf16(const pointer_type input) noexcept -> size_type + { + return length_for_utf16({input, std::char_traits::length(input)}); + } + + auto length_for_utf32(const input_type input) noexcept -> size_type + { + return ::latin::scalar::length(input); + } + + auto length_for_utf32(const pointer_type input) noexcept -> size_type + { + return length_for_utf32({input, std::char_traits::length(input)}); + } + + auto write_utf8( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::latin::scalar::write_utf8(output, input); + } + + auto write_utf8( + const output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf8(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::latin::scalar::write_utf8(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf8_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::latin::scalar::write_utf8(output, input); + return {.output = result.output}; + } + + auto write_utf8_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf8_correct(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::latin::scalar::write_utf8(output, input); + } + + auto write_utf8( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf8(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::latin::scalar::write_utf8(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf8_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::latin::scalar::write_utf8(output, input); + return {.output = result.output}; + } + + auto write_utf8_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf8_correct(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::latin::scalar::write_utf16(output, input); + } + + auto write_utf16_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf16_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::latin::scalar::write_utf16(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf16_le_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_le_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::latin::scalar::write_utf16(output, input); + return {.output = result.output}; + } + + auto write_utf16_le_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf16_le_correct(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::latin::scalar::write_utf16(output, input); + } + + auto write_utf16_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf16_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::latin::scalar::write_utf16(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf16_be_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_be_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::latin::scalar::write_utf16(output, input); + return {.output = result.output}; + } + + auto write_utf16_be_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf16_be_correct(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::latin::scalar::write_utf32(output, input); + } + + auto write_utf32( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf32(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::latin::scalar::write_utf32(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf32_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf32_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::latin::scalar::write_utf32(output, input); + return {.output = result.output}; + } + + auto write_utf32_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf32_correct(output, {input, std::char_traits::length(input)}); + } + } + } + + namespace utf8_char + { + [[nodiscard]] auto validate(const pointer_type current, const pointer_type end) noexcept -> std::pair + { + return ::utf8::validate(current, end); + } + + [[nodiscard]] auto write_latin( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_latin(output, current, end); + } + + [[nodiscard]] auto write_latin_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_latin(output, current, end); + } + + [[nodiscard]] auto write_latin_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_latin(output, current, end); + } + + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_le_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_le_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_be_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_be_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf32( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_utf32(output, current, end); + } + + [[nodiscard]] auto write_utf32_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_utf32(output, current, end); + } + + [[nodiscard]] auto write_utf32_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_utf32(output, current, end); + } + + [[nodiscard]] auto write_utf8( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::transform(output, current, end); + } + + [[nodiscard]] auto write_utf8_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::transform(output, current, end); + } + + [[nodiscard]] auto write_utf8_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::transform(output, current, end); + } + + namespace scalar + { + auto validate(const input_type input) noexcept -> result_error_input_type + { + return ::utf8::scalar::validate(input); + } + + auto validate(const pointer_type input) noexcept -> result_error_input_type + { + return validate({input, std::char_traits::length(input)}); + } + + auto rewind_and_validate(const pointer_type begin, const pointer_type current, const pointer_type end) noexcept -> result_error_input_type + { + return ::utf8::scalar::rewind_and_validate(begin, current, end); + } + + auto length_for_latin(const input_type input) noexcept -> size_type + { + return ::utf8::scalar::length(input); + } + + auto length_for_latin(const pointer_type input) noexcept -> size_type + { + return length_for_latin({input, std::char_traits::length(input)}); + } + + auto length_for_utf8(const input_type input) noexcept -> size_type + { + return input.size(); + } + + auto length_for_utf8(const pointer_type input) noexcept -> size_type + { + return length_for_utf8({input, std::char_traits::length(input)}); + } + + auto length_for_utf16(const input_type input) noexcept -> size_type + { + const auto length = ::utf8::scalar::length(input); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME((length == ::utf8::scalar::length(input))); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME((length == ::utf8::scalar::length(input))); + + return length; + } + + auto length_for_utf16(const pointer_type input) noexcept -> size_type + { + return length_for_utf16({input, std::char_traits::length(input)}); + } + + auto length_for_utf32(const input_type input) noexcept -> size_type + { + return ::utf8::scalar::length(input); + } + + auto length_for_utf32(const pointer_type input) noexcept -> size_type + { + return length_for_utf32({input, std::char_traits::length(input)}); + } + + auto write_latin( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::scalar::write_latin(output, input); + } + + auto write_latin( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_latin(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf8::scalar::write_latin(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_latin_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_latin_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf8::scalar::write_latin(output, input); + return {.output = result.output}; + } + + auto write_latin_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_latin_correct(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::scalar::write_utf16(output, input); + } + + auto write_utf16_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf16_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf8::scalar::write_utf16(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf16_le_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_le_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf8::scalar::write_utf16(output, input); + return {.output = result.output}; + } + + auto write_utf16_le_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf16_le_correct(output, {input, std::char_traits::length(input)}); + } + + auto rewind_and_write_utf16_le( + const output_type_of::pointer output, + const pointer_type furthest_possible_begin, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::scalar::rewind_and_convert(output, furthest_possible_begin, input); + } + + auto write_utf16_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::scalar::write_utf16(output, input); + } + + auto write_utf16_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf16_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf8::scalar::write_utf16(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf16_be_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_be_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf8::scalar::write_utf16(output, input); + return {.output = result.output}; + } + + auto write_utf16_be_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf16_be_correct(output, {input, std::char_traits::length(input)}); + } + + auto rewind_and_write_utf16_be( + const output_type_of::pointer output, + const pointer_type furthest_possible_begin, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::scalar::rewind_and_convert(output, furthest_possible_begin, input); + } + + auto write_utf32( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::scalar::write_utf32(output, input); + } + + auto write_utf32( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf32(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf8::scalar::write_utf32(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf32_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf32_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf8::scalar::write_utf32(output, input); + return {.output = result.output}; + } + + auto write_utf32_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf32_correct(output, {input, std::char_traits::length(input)}); + } + + auto rewind_and_write_utf32( + const output_type_of::pointer output, + const pointer_type furthest_possible_begin, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::scalar::rewind_and_convert(output, furthest_possible_begin, input); + } + + auto write_utf8( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + return ::utf8::scalar::transform(output, input); + } + + auto write_utf8( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8(output, {input, std::char_traits::length(input)}); + } + } + } + + namespace utf8 + { + [[nodiscard]] auto validate(const pointer_type current, const pointer_type end) noexcept -> std::pair + { + return ::utf8::validate(current, end); + } + + [[nodiscard]] auto write_latin( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_latin(output, current, end); + } + + [[nodiscard]] auto write_latin_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_latin(output, current, end); + } + + [[nodiscard]] auto write_latin_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_latin(output, current, end); + } + + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_le_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_le_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_be_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_be_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf32( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_utf32(output, current, end); + } + + [[nodiscard]] auto write_utf32_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_utf32(output, current, end); + } + + [[nodiscard]] auto write_utf32_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::write_utf32(output, current, end); + } + + [[nodiscard]] auto write_utf8( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::transform(output, current, end); + } + + [[nodiscard]] auto write_utf8_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::transform(output, current, end); + } + + [[nodiscard]] auto write_utf8_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf8::transform(output, current, end); + } + + namespace scalar + { + auto validate(const input_type input) noexcept -> result_error_input_type + { + return ::utf8::scalar::validate(input); + } + + auto validate(const pointer_type input) noexcept -> result_error_input_type + { + return validate({input, std::char_traits::length(input)}); + } + + auto rewind_and_validate(const pointer_type begin, const pointer_type current, const pointer_type end) noexcept -> result_error_input_type + { + return ::utf8::scalar::rewind_and_validate(begin, current, end); + } + + auto length_for_latin(const input_type input) noexcept -> size_type + { + return ::utf8::scalar::length(input); + } + + auto length_for_latin(const pointer_type input) noexcept -> size_type + { + return length_for_latin({input, std::char_traits::length(input)}); + } + + auto length_for_utf8(const input_type input) noexcept -> size_type + { + return input.size(); + } + + auto length_for_utf8(const pointer_type input) noexcept -> size_type + { + return length_for_utf8({input, std::char_traits::length(input)}); + } + + auto length_for_utf16(const input_type input) noexcept -> size_type + { + const auto length = ::utf8::scalar::length(input); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME((length == ::utf8::scalar::length(input))); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME((length == ::utf8::scalar::length(input))); + + return length; + } + + auto length_for_utf16(const pointer_type input) noexcept -> size_type + { + return length_for_utf16({input, std::char_traits::length(input)}); + } + + auto length_for_utf32(const input_type input) noexcept -> size_type + { + return ::utf8::scalar::length(input); + } + + auto length_for_utf32(pointer_type input) noexcept -> size_type + { + return length_for_utf32({input, std::char_traits::length(input)}); + } + + auto write_latin( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::scalar::write_latin(output, input); + } + + auto write_latin( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_latin(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf8::scalar::write_latin(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_latin_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_latin_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf8::scalar::write_latin(output, input); + return {.output = result.output}; + } + + auto write_latin_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_latin_correct(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::scalar::write_utf16(output, input); + } + + auto write_utf16_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf16_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf8::scalar::write_utf16(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf16_le_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_le_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf8::scalar::write_utf16(output, input); + return {.output = result.output}; + } + + auto write_utf16_le_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf16_le_correct(output, {input, std::char_traits::length(input)}); + } + + auto rewind_and_write_utf16_le( + const output_type_of::pointer output, + const pointer_type furthest_possible_begin, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::scalar::rewind_and_convert(output, furthest_possible_begin, input); + } + + auto write_utf16_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::scalar::write_utf16(output, input); + } + + auto write_utf16_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf16_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf8::scalar::write_utf16(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf16_be_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_be_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf8::scalar::write_utf16(output, input); + return {.output = result.output}; + } + + auto write_utf16_be_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf16_be_correct(output, {input, std::char_traits::length(input)}); + } + + auto rewind_and_write_utf16_be( + const output_type_of::pointer output, + const pointer_type furthest_possible_begin, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::scalar::rewind_and_convert(output, furthest_possible_begin, input); + } + + auto write_utf32( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::scalar::write_utf32(output, input); + } + + auto write_utf32( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf32(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf8::scalar::write_utf32(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf32_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf32_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf8::scalar::write_utf32(output, input); + return {.output = result.output}; + } + + auto write_utf32_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf32_correct(output, {input, std::char_traits::length(input)}); + } + + auto rewind_and_write_utf32( + const output_type_of::pointer output, + const pointer_type furthest_possible_begin, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf8::scalar::rewind_and_convert(output, furthest_possible_begin, input); + } + + auto write_utf8( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + return ::utf8::scalar::transform(output, input); + } + + auto write_utf8( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8(output, {input, std::char_traits::length(input)}); + } + } + } + + namespace utf16 + { + [[nodiscard]] auto validate_le(const pointer_type current, const pointer_type end) noexcept -> std::pair + { + return ::utf16::validate(current, end); + } + + [[nodiscard]] auto validate_be(const pointer_type current, const pointer_type end) noexcept -> std::pair + { + return ::utf16::validate(current, end); + } + + [[nodiscard]] auto write_latin_le( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_latin(output, current, end); + } + + [[nodiscard]] auto write_latin_be( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_latin(output, current, end); + } + + [[nodiscard]] auto write_latin_pure_le( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_latin(output, current, end); + } + + [[nodiscard]] auto write_latin_pure_be( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_latin(output, current, end); + } + + [[nodiscard]] auto write_latin_correct_le( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_latin(output, current, end); + } + + [[nodiscard]] auto write_latin_correct_be( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_latin(output, current, end); + } + + [[nodiscard]] auto write_utf8_le( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_be( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_pure_le( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_pure_be( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_correct_le( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_correct_be( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_le( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_be( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_pure_le( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_pure_be( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_correct_le( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_correct_be( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf32_le( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_utf32(output, current, end); + } + + [[nodiscard]] auto write_utf32_be( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_utf32(output, current, end); + } + + [[nodiscard]] auto write_utf32_pure_le( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_utf32(output, current, end); + } + + [[nodiscard]] auto write_utf32_pure_be( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_utf32(output, current, end); + } + + [[nodiscard]] auto write_utf32_correct_le( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_utf32(output, current, end); + } + + [[nodiscard]] auto write_utf32_correct_be( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf16::write_utf32(output, current, end); + } + + namespace scalar + { + [[nodiscard]] auto validate_le(const input_type input) noexcept -> result_error_input_type + { + return ::utf16::scalar::validate(input); + } + + [[nodiscard]] auto validate_le(const pointer_type input) noexcept -> result_error_input_type + { + return validate_le({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto validate_be(const input_type input) noexcept -> result_error_input_type + { + return ::utf16::scalar::validate(input); + } + + [[nodiscard]] auto validate_be(const pointer_type input) noexcept -> result_error_input_type + { + return validate_be({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_le_for_latin(const input_type input) noexcept -> size_type + { + return ::utf16::scalar::length(input); + } + + [[nodiscard]] auto length_le_for_latin(const pointer_type input) noexcept -> size_type + { + return length_le_for_latin({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_be_for_latin(const input_type input) noexcept -> size_type + { + return ::utf16::scalar::length(input); + } + + [[nodiscard]] auto length_be_for_latin(const pointer_type input) noexcept -> size_type + { + return length_be_for_latin({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_le_for_utf8(const input_type input) noexcept -> size_type + { + return ::utf16::scalar::length(input); + } + + [[nodiscard]] auto length_le_for_utf8(const pointer_type input) noexcept -> size_type + { + return length_le_for_utf8({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_be_for_utf8(const input_type input) noexcept -> size_type + { + return ::utf16::scalar::length(input); + } + + [[nodiscard]] auto length_be_for_utf8(const pointer_type input) noexcept -> size_type + { + return length_be_for_utf8({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_for_utf16(const input_type input) noexcept -> size_type + { + return input.size(); + } + + [[nodiscard]] auto length_for_utf16(const pointer_type input) noexcept -> size_type + { + return length_for_utf16({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_le_for_utf32(const input_type input) noexcept -> size_type + { + return ::utf16::scalar::length(input); + } + + [[nodiscard]] auto length_le_for_utf32(const pointer_type input) noexcept -> size_type + { + return length_le_for_utf32({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_be_for_utf32(const input_type input) noexcept -> size_type + { + return ::utf16::scalar::length(input); + } + + [[nodiscard]] auto length_be_for_utf32(const pointer_type input) noexcept -> size_type + { + return length_be_for_utf32({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_latin_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf16::scalar::write_latin(output, input); + } + + [[nodiscard]] auto write_latin_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_latin_le(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_latin_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf16::scalar::write_latin(output, input); + } + + [[nodiscard]] auto write_latin_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_latin_be(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_pure_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf16::scalar::write_latin(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_latin_pure_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_latin_pure_le(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_pure_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf16::scalar::write_latin(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_latin_pure_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_latin_pure_be(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_correct_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf16::scalar::write_latin(output, input); + return {.output = result.output}; + } + + auto write_latin_correct_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_latin_correct_le(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_correct_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf16::scalar::write_latin(output, input); + return {.output = result.output}; + } + + auto write_latin_correct_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_latin_correct_be(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf8_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf16::scalar::write_utf8(output, input); + } + + [[nodiscard]] auto write_utf8_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf8_le(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf8_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf16::scalar::write_utf8(output, input); + } + + [[nodiscard]] auto write_utf8_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf8_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_pure_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf16::scalar::write_utf8(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf8_pure_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8_pure_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_pure_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf16::scalar::write_utf8(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf8_pure_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8_pure_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_correct_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf16::scalar::write_utf8(output, input); + return {.output = result.output}; + } + + auto write_utf8_correct_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf8_correct_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_correct_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf16::scalar::write_utf8(output, input); + return {.output = result.output}; + } + + auto write_utf8_correct_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf8_correct_be(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf8_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf16::scalar::write_utf8(output, input); + } + + [[nodiscard]] auto write_utf8_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf8_le(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf8_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf16::scalar::write_utf8(output, input); + } + + [[nodiscard]] auto write_utf8_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf8_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_pure_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf16::scalar::write_utf8(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf8_pure_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8_pure_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_pure_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf16::scalar::write_utf8(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf8_pure_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8_pure_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_correct_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf16::scalar::write_utf8(output, input); + return {.output = result.output}; + } + + auto write_utf8_correct_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf8_correct_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_correct_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf16::scalar::write_utf8(output, input); + return {.output = result.output}; + } + + auto write_utf8_correct_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf8_correct_be(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf32_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf16::scalar::write_utf32(output, input); + } + + [[nodiscard]] auto write_utf32_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf32_le(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf32_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf16::scalar::write_utf32(output, input); + } + + [[nodiscard]] auto write_utf32_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf32_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_pure_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf16::scalar::write_utf32(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf32_pure_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf32_pure_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_pure_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf16::scalar::write_utf32(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf32_pure_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf32_pure_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_correct_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf16::scalar::write_utf32(output, input); + return {.output = result.output}; + } + + auto write_utf32_correct_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf32_correct_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf32_correct_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf16::scalar::write_utf32(output, input); + return {.output = result.output}; + } + + auto write_utf32_correct_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf32_correct_be(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf16_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + return ::utf16::scalar::transform(output, input); + } + + [[nodiscard]] auto write_utf16_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_le(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf16_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + return ::utf16::scalar::transform(output, input); + } + + [[nodiscard]] auto write_utf16_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_be(output, {input, std::char_traits::length(input)}); + } + + auto flip( + const output_type_of::pointer output, + const input_type input + ) noexcept -> void + { + return ::utf16::scalar::flip(output, input); + } + + auto flip( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> void + { + return flip(output, {input, std::char_traits::length(input)}); + } + } + } + + namespace utf32 + { + [[nodiscard]] auto validate(const pointer_type current, const pointer_type end) noexcept -> std::pair + { + return ::utf32::validate(current, end); + } + + [[nodiscard]] auto write_latin( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf32::write_latin(output, current, end); + } + + [[nodiscard]] auto write_latin_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf32::write_latin(output, current, end); + } + + [[nodiscard]] auto write_latin_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf32::write_latin(output, current, end); + } + + [[nodiscard]] auto write_utf8( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf32::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf32::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf32::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf32::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf32::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf8_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf32::write_utf8(output, current, end); + } + + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf32::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_le_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf32::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_le_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf32::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf32::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_be_pure( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf32::write_utf16(output, current, end); + } + + [[nodiscard]] auto write_utf16_be_correct( + output_type_of::pointer& output, + const pointer_type current, + const pointer_type end + ) noexcept -> std::pair + { + return ::utf32::write_utf16(output, current, end); + } + + namespace scalar + { + [[nodiscard]] auto validate(const input_type input) noexcept -> result_error_input_type + { + return ::utf32::scalar::validate(input); + } + + [[nodiscard]] auto validate(const pointer_type input) noexcept -> result_error_input_type + { + return validate({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_for_latin(const input_type input) noexcept -> size_type + { + return ::utf32::scalar::length(input); + } + + [[nodiscard]] auto length_for_latin(const pointer_type input) noexcept -> size_type + { + return length_for_latin({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_for_utf8(const input_type input) noexcept -> size_type + { + const auto length = ::utf32::scalar::length(input); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length == ::utf32::scalar::length(input)); + + return length; + } + + [[nodiscard]] auto length_for_utf8(const pointer_type input) noexcept -> size_type + { + return length_for_utf8({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_for_utf16(const input_type input) noexcept -> size_type + { + const auto length = ::utf32::scalar::length(input); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length == ::utf32::scalar::length(input)); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(length == ::utf32::scalar::length(input)); + + return length; + } + + [[nodiscard]] auto length_for_utf16(const pointer_type input) noexcept -> size_type + { + return length_for_utf16({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto length_for_utf32(const input_type input) noexcept -> size_type + { + return input.size(); + } + + [[nodiscard]] auto length_for_utf32(const pointer_type input) noexcept -> size_type + { + return length_for_utf32({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_latin( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf32::scalar::write_latin(output, input); + } + + [[nodiscard]] auto write_latin( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_latin(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf32::scalar::write_latin(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_latin_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_latin_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_latin_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf32::scalar::write_latin(output, input); + return {.output = result.output}; + } + + auto write_latin_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_latin_correct(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf8( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf32::scalar::write_utf8(output, input); + } + + [[nodiscard]] auto write_utf8( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf8(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf32::scalar::write_utf8(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf8_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf32::scalar::write_utf8(output, input); + return {.output = result.output}; + } + + auto write_utf8_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf8_correct(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf8( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf32::scalar::write_utf8(output, input); + } + + [[nodiscard]] auto write_utf8( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf8(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf32::scalar::write_utf8(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf8_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf8_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf8_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf32::scalar::write_utf8(output, input); + return {.output = result.output}; + } + + auto write_utf8_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf8_correct(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf16_le( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf32::scalar::write_utf16(output, input); + } + + [[nodiscard]] auto write_utf16_le( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf16_le(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf32::scalar::write_utf16(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf16_le_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_le_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_le_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf32::scalar::write_utf16(output, input); + return {.output = result.output}; + } + + auto write_utf16_le_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf16_le_correct(output, {input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto write_utf16_be( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_output_type + { + return ::utf32::scalar::write_utf16(output, input); + } + + [[nodiscard]] auto write_utf16_be( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_output_type + { + return write_utf16_be(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be_pure( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_error_input_type + { + const auto result = ::utf32::scalar::write_utf16(output, input); + return {.error = result.error, .input = result.input}; + } + + auto write_utf16_be_pure( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_error_input_type + { + return write_utf16_be_pure(output, {input, std::char_traits::length(input)}); + } + + auto write_utf16_be_correct( + const output_type_of::pointer output, + const input_type input + ) noexcept -> result_output_type + { + const auto result = ::utf32::scalar::write_utf16(output, input); + return {.output = result.output}; + } + + auto write_utf16_be_correct( + const output_type_of::pointer output, + const pointer_type input + ) noexcept -> result_output_type + { + return write_utf16_be_correct(output, {input, std::char_traits::length(input)}); + } + } + } + + [[nodiscard]] auto Scalar::encoding_of(const std::span input) noexcept -> EncodingType + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); + + if (const auto bom = bom_of(input); + bom != EncodingType::UNKNOWN) + { + return bom; + } + + const auto input_length = input.size(); + + const auto it_input_begin = input.data(); + // auto it_input_current = it_input_begin; + // const auto it_input_end = it_input_begin + input_length; + + // utf8 + bool utf8 = true; + // utf16 + bool utf16 = (input_length % 2) == 0; + // utf32 + bool utf32 = (input_length % 4) == 0; + + // UTF8 + if (utf8) + { + utf8 = not validate(input).has_error(); + } + + // UTF16 + if (utf16) + { + const auto* p16 = GAL_PROMETHEUS_SEMANTIC_UNRESTRICTED_CHAR_POINTER_CAST(utf16::char_type, it_input_begin); + utf16 = not validate({p16, input_length / 2}).has_error(); + } + + // UTF32 + if (utf32) + { + const auto p32 = GAL_PROMETHEUS_SEMANTIC_UNRESTRICTED_CHAR_POINTER_CAST(utf32::char_type, it_input_begin); + utf32 = not validate({p32, input_length / 4}).has_error(); + } + + auto all_possible = std::to_underlying(EncodingType::UNKNOWN); + + if (utf8) + { + all_possible |= std::to_underlying(EncodingType::UTF8); + } + + if (utf16) + { + all_possible |= std::to_underlying(EncodingType::UTF16_LE); + } + + if (utf32) + { + all_possible |= std::to_underlying(EncodingType::UTF32_LE); + } + + return static_cast(all_possible); + } + + [[nodiscard]] auto Scalar::encoding_of(const std::span input) noexcept -> EncodingType + { + static_assert(sizeof(char) == sizeof(char8_t)); + + const auto* char8_string = GAL_PROMETHEUS_SEMANTIC_UNRESTRICTED_CHAR_POINTER_CAST(char8_t, input.data()); + return encoding_of({char8_string, input.size()}); + } +} diff --git a/src/chars/scalar.hpp b/src/chars/scalar.hpp new file mode 100644 index 00000000..ce06d0f1 --- /dev/null +++ b/src/chars/scalar.hpp @@ -0,0 +1,8919 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include + +namespace gal::prometheus::chars +{ + namespace latin::scalar + { + /** + * @brief Checks if there are all valid `ASCII` code point in the range of @c input. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate(input_type input) noexcept -> result_error_input_type; + + /** + * @brief Checks if there are all valid `ASCII` code point in the range of [@c input, @c input+std::char_traits::length(input)]. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate(pointer_type input) noexcept -> result_error_input_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_for_latin(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_for_latin(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf8(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf8(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf32(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf32(pointer_type input) noexcept -> size_type; + + // ======================================================= + // LATIN => UTF8_CHAR + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf8(input)); + + std::ignore = scalar::write_utf8(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_char({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf8(input) ==> input.size() + string.resize(length_for_utf8(input)); + + std::ignore = scalar::write_utf8_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_char_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf8(input)); + + std::ignore = scalar::write_utf8_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_char_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // LATIN => UTF8 + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf8(input)); + + std::ignore = scalar::write_utf8(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf8(input) ==> input.size() + string.resize(length_for_utf8(input)); + + std::ignore = scalar::write_utf8_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf8(input)); + + std::ignore = scalar::write_utf8_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // LATIN => UTF16_LE + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_le_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_le_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_le_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_pure(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_le_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_le_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_le_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_le_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_correct(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_le_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // LATIN => UTF16_BE + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_be_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_be_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_be_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_pure(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_be_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_be_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_be_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_be_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_correct(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_be_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // LATIN => UTF32 + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf32(input)); + + std::ignore = scalar::write_utf32(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf32({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf32(input) ==> input.size() + string.resize(length_for_utf32(input)); + + std::ignore = scalar::write_utf32_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf32_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf32(input)); + + std::ignore = scalar::write_utf32_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf32_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_correct({input, std::char_traits::length(input)}); + } + } + + namespace utf8_char::scalar + { + /** + * @brief Checks if there are all valid `UTF8` code point in the range of @c input. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate(input_type input) noexcept -> result_error_input_type; + + /** + * @brief Checks if there are all valid `UTF8` code point in the range of [@c input, @c input+std::char_traits::length(input)]. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate(pointer_type input) noexcept -> result_error_input_type; + + /** + * @brief Finds the previous leading byte starting backward from @c current and validates from there. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note Used to pinpoint the location of an error when an invalid chunk is detected. + * @note We assume that the stream starts with a leading byte, and to check that it is the case, + * we ask that you pass a pointer to the start of the stream (@c begin). + */ + [[nodiscard]] auto rewind_and_validate(pointer_type begin, pointer_type current, pointer_type end) noexcept -> result_error_input_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_for_latin(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_for_latin(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf8(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf8(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf32(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf32(pointer_type input) noexcept -> size_type; + + // ======================================================= + // UTF8 => LATIN + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_latin(input)); + + std::ignore = scalar::write_latin(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin(const pointer_type input) noexcept -> StringType + { + return scalar::write_latin({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_latin(input) ==> input.size() + string.resize(length_for_latin(input)); + + std::ignore = scalar::write_latin_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure(const pointer_type input) noexcept -> StringType + { + return scalar::write_latin_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_latin(input)); + + std::ignore = scalar::write_latin_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct(const pointer_type input) noexcept -> StringType + { + return scalar::write_latin_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF8 => UTF16_LE + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_le_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_le_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_le_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_pure(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_le_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_le_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_le_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_le_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_correct(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_le_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le_correct({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto rewind_and_write_utf16_le( + output_type_of::pointer output, + pointer_type furthest_possible_begin, + input_type input + ) noexcept -> result_error_input_output_type; + + // ======================================================= + // UTF8 => UTF16_BE + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_be_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_be_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_be_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_pure(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_be_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_be_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_be_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_be_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_correct(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_be_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be_correct({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto rewind_and_write_utf16_be( + output_type_of::pointer output, + pointer_type furthest_possible_begin, + input_type input + ) noexcept -> result_error_input_output_type; + + // ======================================================= + // UTF8 => UTF32 + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf32(input)); + + std::ignore = scalar::write_utf32(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf32({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf32(input) ==> input.size() + string.resize(length_for_utf32(input)); + + std::ignore = scalar::write_utf32_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf32_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf32(input)); + + std::ignore = scalar::write_utf32_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf32_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_correct({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto rewind_and_write_utf32( + output_type_of::pointer output, + pointer_type furthest_possible_begin, + input_type input + ) noexcept -> result_error_input_output_type; + + // ======================================================= + // UTF8_CHAR => UTF8 + + /** + * @brief Convert UTF8_CHAR string to UTF8 string up to the first invalid UTF8 code point. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Convert UTF8_CHAR string to UTF8 string up to the first invalid UTF8 code point. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Convert UTF8_CHAR string to UTF8 string up to the first invalid UTF8 code point. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf8(input) ==> input.size() + string.resize(length_for_utf8(input)); + + std::ignore = scalar::write_utf8(string.data(), input); + + return string; + } + + /** + * @brief Convert UTF8_CHAR string to UTF8 string up to the first invalid UTF8 code point. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8({input, std::char_traits::length(input)}); + } + + /** + * @brief Convert UTF8_CHAR string to UTF8 string up to the first invalid UTF8 code point. + */ + [[nodiscard]] inline auto write_utf8(const input_type input) noexcept -> std::basic_string + { + return scalar::write_utf8>(input); + } + + /** + * @brief Convert UTF8_CHAR string to UTF8 string up to the first invalid UTF8 code point. + */ + [[nodiscard]] inline auto write_utf8(const pointer_type input) noexcept -> std::basic_string + { + return scalar::write_utf8({input, std::char_traits::length(input)}); + } + } + + namespace utf8::scalar + { + /** + * @brief Checks if there are all valid `UTF8` code point in the range of @c input. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate(input_type input) noexcept -> result_error_input_type; + + /** + * @brief Checks if there are all valid `UTF8` code point in the range of [@c input, @c input+std::char_traits::length(input)]. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate(pointer_type input) noexcept -> result_error_input_type; + + /** + * @brief Finds the previous leading byte starting backward from @c current and validates from there. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note Used to pinpoint the location of an error when an invalid chunk is detected. + * @note We assume that the stream starts with a leading byte, and to check that it is the case, + * we ask that you pass a pointer to the start of the stream (@c begin). + */ + [[nodiscard]] auto rewind_and_validate(pointer_type begin, pointer_type current, pointer_type end) noexcept -> result_error_input_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_for_latin(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_for_latin(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf8(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf8(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf32(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf32(pointer_type input) noexcept -> size_type; + + // ======================================================= + // UTF8 => LATIN + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_latin(input)); + + std::ignore = scalar::write_latin(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin(const pointer_type input) noexcept -> StringType + { + return scalar::write_latin({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_latin(input) ==> input.size() + string.resize(length_for_latin(input)); + + std::ignore = scalar::write_latin_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure(const pointer_type input) noexcept -> StringType + { + return scalar::write_latin_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_latin(input)); + + std::ignore = scalar::write_latin_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct(const pointer_type input) noexcept -> StringType + { + return scalar::write_latin_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF8 => UTF16_LE + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_le_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_le_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_le_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_pure(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_le_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_le_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_le_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_le_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_correct(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_le_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le_correct({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto rewind_and_write_utf16_le( + output_type_of::pointer output, + pointer_type furthest_possible_begin, + input_type input + ) noexcept -> result_error_input_output_type; + + // ======================================================= + // UTF8 => UTF16_BE + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_be_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_be_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_be_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_pure(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_be_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_be_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_be_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_be_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_correct(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_be_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be_correct({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto rewind_and_write_utf16_be( + output_type_of::pointer output, + pointer_type furthest_possible_begin, + input_type input + ) noexcept -> result_error_input_output_type; + + // ======================================================= + // UTF8 => UTF32 + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf32(input)); + + std::ignore = scalar::write_utf32(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf32({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf32(input) ==> input.size() + string.resize(length_for_utf32(input)); + + std::ignore = scalar::write_utf32_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf32_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf32(input)); + + std::ignore = scalar::write_utf32_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf32_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_correct({input, std::char_traits::length(input)}); + } + + [[nodiscard]] auto rewind_and_write_utf32( + output_type_of::pointer output, + pointer_type furthest_possible_begin, + input_type input + ) noexcept -> result_error_input_output_type; + + // ======================================================= + // UTF8 => UTF8_CHAR + + /** + * @brief Convert UTF8 string to UTF8_CHAR string up to the first invalid UTF8 code point. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Convert UTF8 string to UTF8_CHAR string up to the first invalid UTF8 code point. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Convert UTF8 string to UTF8_CHAR string up to the first invalid UTF8 code point. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf8(input)); + + std::ignore = scalar::write_utf8(string.data(), input); + + return string; + } + + /** + * @brief Convert UTF8 string to UTF8_CHAR string up to the first invalid UTF8 code point. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8({input, std::char_traits::length(input)}); + } + + /** + * @brief Convert UTF8 string to UTF8_CHAR string up to the first invalid UTF8 code point. + */ + [[nodiscard]] inline auto write_utf8(const input_type input) noexcept -> std::basic_string + { + return scalar::write_utf8>(input); + } + + /** + * @brief Convert UTF8 string to UTF8_CHAR string up to the first invalid UTF8 code point. + */ + [[nodiscard]] inline auto write_utf8(const pointer_type input) noexcept -> std::basic_string + { + return scalar::write_utf8({input, std::char_traits::length(input)}); + } + } + + namespace utf16::scalar + { + /** + * @brief Checks if there are all valid `UTF16 (little-endian)` code point in the range of @c input. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate_le(input_type input) noexcept -> result_error_input_type; + + /** + * @brief Checks if there are all valid `UTF16 (little-endian)` code point in the range of [@c input, @c input+std::char_traits::length(input)]. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate_le(pointer_type input) noexcept -> result_error_input_type; + + /** + * @brief Checks if there are all valid `UTF16 (big-endian)` code point in the range of @c input. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate_be(input_type input) noexcept -> result_error_input_type; + + /** + * @brief Checks if there are all valid `UTF16 (big-endian)` code point in the range of [@c input, @c input+std::char_traits::length(input)]. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate_be(pointer_type input) noexcept -> result_error_input_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_le_for_latin(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_le_for_latin(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_be_for_latin(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_be_for_latin(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_le_for_utf8(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_le_for_utf8(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_be_for_utf8(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_be_for_utf8(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_le_for_utf32(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_le_for_utf32(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_be_for_utf32(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_be_for_utf32(pointer_type input) noexcept -> size_type; + + // ======================================================= + // UTF16 => LATIN + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_le_for_latin(input)); + + std::ignore = scalar::write_latin_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_le(const pointer_type input) noexcept -> StringType + { + return scalar::write_latin_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_be_for_latin(input)); + + std::ignore = scalar::write_latin_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_be(const pointer_type input) noexcept -> StringType + { + return scalar::write_latin_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure_le(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_le_for_latin(input) ==> input.size() + string.resize(length_le_for_latin(input)); + + std::ignore = scalar::write_latin_pure_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure_le(const pointer_type input) noexcept -> StringType + { + return scalar::write_latin_pure_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_pure_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_pure_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure_be(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_be_for_latin(input) ==> input.size() + string.resize(length_be_for_latin(input)); + + std::ignore = scalar::write_latin_pure_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure_be(const pointer_type input) noexcept -> StringType + { + return scalar::write_latin_pure_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_pure_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_pure_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_le_for_latin(input)); + + std::ignore = scalar::write_latin_correct_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct_le(const pointer_type input) noexcept -> StringType + { + return scalar::write_latin_correct_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_correct_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_correct_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_be_for_latin(input)); + + std::ignore = scalar::write_latin_correct_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct_be(const pointer_type input) noexcept -> StringType + { + return scalar::write_latin_correct_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_correct_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_correct_be({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF16 => UTF8_CHAR + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_le_for_utf8(input)); + + std::ignore = scalar::write_utf8_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_le(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_char_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_be_for_utf8(input)); + + std::ignore = scalar::write_utf8_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_be(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_char_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure_le(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_le_for_utf8(input) ==> input.size() + string.resize(length_le_for_utf8(input)); + + std::ignore = scalar::write_utf8_pure_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure_le(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_pure_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_pure_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_pure_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_pure_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_char_pure_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure_be(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_be_for_utf8(input) ==> input.size() + string.resize(length_be_for_utf8(input)); + + std::ignore = scalar::write_utf8_pure_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure_be(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_pure_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_pure_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_pure_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_pure_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_char_pure_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_le_for_utf8(input)); + + std::ignore = scalar::write_utf8_correct_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct_le(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_correct_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_correct_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_correct_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_correct_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_char_correct_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_be_for_utf8(input)); + + std::ignore = scalar::write_utf8_correct_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct_be(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_correct_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_correct_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_correct_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_correct_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_char_correct_be({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF16 => UTF8 + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_le_for_utf8(input)); + + std::ignore = scalar::write_utf8_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_le(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_be_for_utf8(input)); + + std::ignore = scalar::write_utf8_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_be(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure_le(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_le_for_utf8(input) ==> input.size() + string.resize(length_le_for_utf8(input)); + + std::ignore = scalar::write_utf8_pure_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure_le(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_pure_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_pure_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_pure_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_pure_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_pure_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure_be(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_be_for_utf8(input) ==> input.size() + string.resize(length_be_for_utf8(input)); + + std::ignore = scalar::write_utf8_pure_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure_be(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_pure_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_pure_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_pure_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_pure_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_pure_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_le_for_utf8(input)); + + std::ignore = scalar::write_utf8_correct_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct_le(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_correct_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_correct_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_correct_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_correct_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_correct_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_be_for_utf8(input)); + + std::ignore = scalar::write_utf8_correct_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct_be(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_correct_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_correct_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_correct_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_correct_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_correct_be({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF16 => UTF32 + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_le_for_utf32(input)); + + std::ignore = scalar::write_utf32_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_le(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf32_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf32(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf32_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_be_for_utf32(input)); + + std::ignore = scalar::write_utf32_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_be(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf32_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure_le(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_le_for_utf32(input) ==> input.size() + string.resize(length_le_for_utf32(input)); + + std::ignore = scalar::write_utf32_pure_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure_le(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf32_pure_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_pure_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_pure_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf32_pure_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure_be(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_be_for_utf32(input) ==> input.size() + string.resize(length_be_for_utf32(input)); + + std::ignore = scalar::write_utf32_pure_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_pure_be(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf32_pure_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_pure_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_pure_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_pure_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_le_for_utf32(input)); + + std::ignore = scalar::write_utf32_correct_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct_le(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf32_correct_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_correct_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_correct_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string, + * assume that the @c input string is all valid `UTF32`. + * @return {@c length_for_utf32(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf32_correct_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_be_for_utf32(input)); + + std::ignore = scalar::write_utf32_correct_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf32_correct_be(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf32_correct_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_correct_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF32` string. + */ + [[nodiscard]] inline auto write_utf32_correct_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf32_correct_be({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF16_LE => UTF16_LE + // UTF16_BE => UTF16_BE + + /** + * @brief Convert UTF16_LE string to UTF16_BE string up to the first invalid UTF16_LE code point. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Convert UTF16_LE string to UTF16_BE string up to the first invalid UTF16_LE code point. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Convert UTF16_LE string to UTF16_BE string up to the first invalid UTF16_LE code point. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_to_be(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_le(string.data(), input); + + return string; + } + + /** + * @brief Convert UTF16_LE string to UTF16_BE string up to the first invalid UTF16_LE code point. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_to_be(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_le_to_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Convert UTF16_LE string to UTF16_BE string up to the first invalid UTF16_LE code point. + */ + [[nodiscard]] inline auto write_utf16_le_to_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le_to_be::value_type>>(input); + } + + /** + * @brief Convert UTF16_LE string to UTF16_BE string up to the first invalid UTF16_LE code point. + */ + [[nodiscard]] inline auto write_utf16_le_to_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le_to_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Convert UTF16_BE string to UTF16_LE string up to the first invalid UTF16_BE code point. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Convert UTF16_BE string to UTF16_LE string up to the first invalid UTF16_BE code point. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Convert UTF16_BE string to UTF16_LE string up to the first invalid UTF16_BE code point. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_to_le(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_be(string.data(), input); + + return string; + } + + /** + * @brief Convert UTF16_BE string to UTF16_LE string up to the first invalid UTF16_BE code point. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_to_le(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_be_to_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Convert UTF16_BE string to UTF16_LE string up to the first invalid UTF16_BE code point. + */ + [[nodiscard]] inline auto write_utf16_be_to_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be_to_le::value_type>>(input); + } + + /** + * @brief Convert UTF16_BE string to UTF16_LE string up to the first invalid UTF16_BE code point. + */ + [[nodiscard]] inline auto write_utf16_be_to_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be_to_le({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF16_LE => UTF16_BE + // UTF16_BE => UTF16_LE + + /** + * @brief Convert UTF16_LE string to UTF16_BE string (or vice versa), assuming the input string is valid. + */ + auto flip( + output_type_of::pointer output, + input_type input + ) noexcept -> void; + + /** + * @brief Convert UTF16_LE string to UTF16_BE string (or vice versa), assuming the input string is valid. + */ + auto flip( + output_type_of::pointer output, + pointer_type input + ) noexcept -> void; + + /** + * @brief Convert UTF16_LE string to UTF16_BE string (or vice versa), assuming the input string is valid. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto flip(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + scalar::flip(string.data(), input); + + return string; + } + + /** + * @brief Convert UTF16_LE string to UTF16_BE string (or vice versa), assuming the input string is valid. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto flip(const pointer_type input) noexcept -> StringType + { + return scalar::flip({input, std::char_traits::length(input)}); + } + + /** + * @brief Convert UTF16_LE string to UTF16_BE string (or vice versa), assuming the input string is valid. + */ + [[nodiscard]] inline auto flip(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::flip::value_type>>(input); + } + + /** + * @brief Convert UTF16_LE string to UTF16_BE string (or vice versa), assuming the input string is valid. + */ + [[nodiscard]] inline auto flip(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::flip({input, std::char_traits::length(input)}); + } + } + + namespace utf32::scalar + { + /** + * @brief Checks if there are all valid `UTF32` code point in the range of @c input. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate(input_type input) noexcept -> result_error_input_type; + + /** + * @brief Checks if there are all valid `UTF32` code point in the range of [@c input, @c input+std::char_traits::length(input)]. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + */ + [[nodiscard]] auto validate(pointer_type input) noexcept -> result_error_input_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_for_latin(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `LATIN` string, how many code points are output. + */ + [[nodiscard]] auto length_for_latin(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf8(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF8` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf8(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF16` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf16(pointer_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf32(input_type input) noexcept -> size_type; + + /** + * @brief If @c input string is converted to a `UTF32` string, how many code points are output. + */ + [[nodiscard]] auto length_for_utf32(pointer_type input) noexcept -> size_type; + + // ======================================================= + // UTF32 => LATIN + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_latin(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_latin( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_latin(input)); + + std::ignore = scalar::write_latin(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin(const pointer_type input) noexcept -> StringType + { + return scalar::write_latin({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_latin_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_latin(input) ==> input.size() + string.resize(length_for_latin(input)); + + std::ignore = scalar::write_latin_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_pure(const pointer_type input) noexcept -> StringType + { + return scalar::write_latin_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string, + * assume that the @c input string is all valid `LATIN`. + * @return {@c length_for_latin(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_latin_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_latin(input)); + + std::ignore = scalar::write_latin_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_latin_correct(const pointer_type input) noexcept -> StringType + { + return scalar::write_latin_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `LATIN` string. + */ + [[nodiscard]] inline auto write_latin_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_latin_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF32 => UTF8_CHAR + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf8(input)); + + std::ignore = scalar::write_utf8(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_char({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf8(input) ==> input.size() + string.resize(length_for_utf8(input)); + + std::ignore = scalar::write_utf8_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_char_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf8(input)); + + std::ignore = scalar::write_utf8_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_char_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_char_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF32 => UTF8 + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf8(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf8( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf8(input)); + + std::ignore = scalar::write_utf8(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf8_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf8(input) ==> input.size() + string.resize(length_for_utf8(input)); + + std::ignore = scalar::write_utf8_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_pure(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string, + * assume that the @c input string is all valid `UTF8`. + * @return {@c length_for_utf8(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf8_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf8(input)); + + std::ignore = scalar::write_utf8_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf8_correct(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf8_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF8` string. + */ + [[nodiscard]] inline auto write_utf8_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf8_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF32 => UTF16_LE + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_le( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_le(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_le_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_le_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_le_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_pure(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_le_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_le_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_le_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_le_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_le_correct(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_le_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_le_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_le_correct({input, std::char_traits::length(input)}); + } + + // ======================================================= + // UTF32 => UTF16_BE + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c input.size(), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input), @c length_for_utf16(input)} + * @return {(error code), (the first invalid code point location), (how many code points are output)} + */ + [[nodiscard]] auto write_utf16_be( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_be(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c input.size()} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_be_pure( + output_type_of::pointer output, + input_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all `ASCII`. + * @return {@c ErrorCode::NONE, @c std::char_traits::length(input)} + * @return {(error code), (the first invalid code point location)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + * @note Each input `ASCII` code always outputs an `ASCII` code, + * so the number of outputs is always equal to the number of inputs. + */ + auto write_utf16_be_pure( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_error_input_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_pure(const input_type input) noexcept -> StringType + { + StringType string{}; + // OPT: length_for_utf16(input) ==> input.size() + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_be_pure(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_pure(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_be_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_pure(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be_pure::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_pure(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be_pure({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_be_correct( + output_type_of::pointer output, + input_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string, + * assume that the @c input string is all valid `UTF16`. + * @return {@c length_for_utf16(input)} + * @note You can expect this function to always succeed (so the function is not marked `nodiscard`). + */ + auto write_utf16_be_correct( + output_type_of::pointer output, + pointer_type input + ) noexcept -> result_output_type; + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_correct(const input_type input) noexcept -> StringType + { + StringType string{}; + string.resize(length_for_utf16(input)); + + std::ignore = scalar::write_utf16_be_correct(string.data(), input); + + return string; + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + template + requires requires(StringType& string) + { + string.resize(std::declval()); + { + string.data() + } -> std::convertible_to::pointer>; + } + [[nodiscard]] auto write_utf16_be_correct(const pointer_type input) noexcept -> StringType + { + return scalar::write_utf16_be_correct({input, std::char_traits::length(input)}); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_correct(const input_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be_correct::value_type>>(input); + } + + /** + * @brief Converts the @c input string `as far as possible` to a `UTF16` string. + */ + [[nodiscard]] inline auto write_utf16_be_correct(const pointer_type input) noexcept -> std::basic_string::value_type> + { + return scalar::write_utf16_be_correct({input, std::char_traits::length(input)}); + } + } + + class Scalar + { + public: + // =================================================== + // encoding + + [[nodiscard]] static auto encoding_of(std::span input) noexcept -> EncodingType; + + [[nodiscard]] static auto encoding_of(std::span input) noexcept -> EncodingType; + + // =================================================== + // validate + + template + [[nodiscard]] constexpr static auto validate( + const typename input_type_of::const_pointer current, + const typename input_type_of::const_pointer end + ) noexcept -> std::pair + { + if constexpr (InputType == CharsType::LATIN) + { + return latin::validate(current, end); + } + else if constexpr (InputType == CharsType::UTF8_CHAR) + { + return utf8_char::validate(current, end); + } + else if constexpr (InputType == CharsType::UTF8) + { + return utf8::validate(current, end); + } + else if constexpr (InputType == CharsType::UTF16_LE) + { + return utf16::validate_le(current, end); + } + else if constexpr (InputType == CharsType::UTF16_BE) + { + return utf16::validate_be(current, end); + } + else if constexpr (InputType == CharsType::UTF32) + { + return utf32::validate(current, end); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + private: + template + [[nodiscard]] constexpr static auto do_validate(const Input input) noexcept -> result_error_input_type + { + if constexpr (InputType == CharsType::LATIN) + { + return latin::scalar::validate(input); + } + else if constexpr (InputType == CharsType::UTF8_CHAR) + { + return utf8_char::scalar::validate(input); + } + else if constexpr (InputType == CharsType::UTF8) + { + return utf8::scalar::validate(input); + } + else if constexpr (InputType == CharsType::UTF16_LE) + { + return utf16::scalar::validate_le(input); + } + else if constexpr (InputType == CharsType::UTF16_BE) + { + return utf16::scalar::validate_be(input); + } + else if constexpr (InputType == CharsType::UTF32) + { + return utf32::scalar::validate(input); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + public: + template + [[nodiscard]] constexpr static auto validate(const input_type_of input) noexcept -> result_error_input_type + { + return Scalar::do_validate(input); + } + + template + [[nodiscard]] constexpr static auto validate(const typename input_type_of::const_pointer input) noexcept -> result_error_input_type + { + return Scalar::do_validate(input); + } + + // =================================================== + // length + + private: + template + [[nodiscard]] constexpr static auto do_length(const Input input) noexcept -> typename input_type_of::size_type + { + if constexpr (InputType == CharsType::LATIN) + { + using namespace latin; + + if constexpr (OutputType == CharsType::LATIN) + { + return scalar::length_for_latin(input); + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + return scalar::length_for_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE or OutputType == CharsType::UTF16_BE or OutputType == CharsType::UTF16) + { + return scalar::length_for_utf16(input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + return scalar::length_for_utf32(input); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF8_CHAR) + { + using namespace utf8_char; + + if constexpr (OutputType == CharsType::LATIN) + { + return scalar::length_for_latin(input); + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + return scalar::length_for_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE or OutputType == CharsType::UTF16_BE or OutputType == CharsType::UTF16) + { + return scalar::length_for_utf16(input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + return scalar::length_for_utf32(input); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF8) + { + using namespace utf8; + + if constexpr (OutputType == CharsType::LATIN) + { + return scalar::length_for_latin(input); + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + return scalar::length_for_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE or OutputType == CharsType::UTF16_BE or OutputType == CharsType::UTF16) + { + return scalar::length_for_utf16(input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + return scalar::length_for_utf32(input); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF16_LE) + { + using namespace utf16; + + if constexpr (OutputType == CharsType::LATIN) + { + return scalar::length_le_for_latin(input); + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + return scalar::length_le_for_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE or OutputType == CharsType::UTF16_BE or OutputType == CharsType::UTF16) + { + return scalar::length_for_utf16(input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + return scalar::length_le_for_utf32(input); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF16_BE) + { + using namespace utf16; + + if constexpr (OutputType == CharsType::LATIN) + { + return scalar::length_be_for_latin(input); + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + return scalar::length_be_for_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE or OutputType == CharsType::UTF16_BE or OutputType == CharsType::UTF16) + { + return scalar::length_for_utf16(input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + return scalar::length_be_for_utf32(input); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF32) + { + using namespace utf32; + + if constexpr (OutputType == CharsType::LATIN) + { + return scalar::length_for_latin(input); + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + return scalar::length_for_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE or OutputType == CharsType::UTF16_BE or OutputType == CharsType::UTF16) + { + return scalar::length_for_utf16(input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + return scalar::length_for_utf32(input); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + public: + template + [[nodiscard]] constexpr static auto length(const input_type_of input) noexcept -> typename input_type_of::size_type + { + return Scalar::do_length(input); + } + + template + [[nodiscard]] constexpr static auto length(const typename input_type_of::const_pointer input) noexcept -> typename input_type_of::size_type + { + return Scalar::do_length(input); + } + + // =================================================== + // write + + template + [[nodiscard]] constexpr static auto convert( + typename output_type_of::pointer& output, + const typename input_type_of::const_pointer current, + const typename input_type_of::const_pointer end + ) noexcept -> std::pair + { + if constexpr (InputType == CharsType::LATIN) + { + if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return latin::write_utf8_pure(output, current, end); + } + else if constexpr (Correct) + { + return latin::write_utf8_correct(output, current, end); + } + else + { + return latin::write_utf8(output, current, end); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return latin::write_utf16_le_pure(output, current, end); + } + else if constexpr (Correct) + { + return latin::write_utf16_le_correct(output, current, end); + } + else + { + return latin::write_utf16_le(output, current, end); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return latin::write_utf16_be_pure(output, current, end); + } + else if constexpr (Correct) + { + return latin::write_utf16_be_correct(output, current, end); + } + else + { + return latin::write_utf16_be(output, current, end); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return latin::write_utf32_pure(output, current, end); + } + else if constexpr (Correct) + { + return latin::write_utf32_correct(output, current, end); + } + else + { + return latin::write_utf32(output, current, end); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF8_CHAR) + { + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return utf8_char::write_latin_pure(output, current, end); + } + else if constexpr (Correct) + { + return utf8_char::write_latin_correct(output, current, end); + } + else + { + return utf8_char::write_latin(output, current, end); + } + } + else if constexpr (OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return utf8_char::write_utf8_pure(output, current, end); + } + else if constexpr (Correct) + { + return utf8_char::write_utf8_correct(output, current, end); + } + else + { + return utf8_char::write_utf8(output, current, end); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return utf8_char::write_utf16_le_pure(output, current, end); + } + else if constexpr (Correct) + { + return utf8_char::write_utf16_le_correct(output, current, end); + } + else + { + return utf8_char::write_utf16_le(output, current, end); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return utf8_char::write_utf16_be_pure(output, current, end); + } + else if constexpr (Correct) + { + return utf8_char::write_utf16_be_correct(output, current, end); + } + else + { + return utf8_char::write_utf16_be(output, current, end); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return utf8_char::write_utf32_pure(output, current, end); + } + else if constexpr (Correct) + { + return utf8_char::write_utf32_correct(output, current, end); + } + else + { + return utf8_char::write_utf32(output, current, end); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF8) + { + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return utf8::write_latin_pure(output, current, end); + } + else if constexpr (Correct) + { + return utf8::write_latin_correct(output, current, end); + } + else + { + return utf8::write_latin(output, current, end); + } + } + else if constexpr (OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return utf8::write_utf8_pure(output, current, end); + } + else if constexpr (Correct) + { + return utf8::write_utf8_correct(output, current, end); + } + else + { + return utf8::write_utf8(output, current, end); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return utf8::write_utf16_le_pure(output, current, end); + } + else if constexpr (Correct) + { + return utf8::write_utf16_le_correct(output, current, end); + } + else + { + return utf8::write_utf16_le(output, current, end); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return utf8::write_utf16_be_pure(output, current, end); + } + else if constexpr (Correct) + { + return utf8::write_utf16_be_correct(output, current, end); + } + else + { + return utf8::write_utf16_be(output, current, end); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return utf8::write_utf32_pure(output, current, end); + } + else if constexpr (Correct) + { + return utf8::write_utf32_correct(output, current, end); + } + else + { + return utf8::write_utf32(output, current, end); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF16_LE) + { + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return utf16::write_latin_pure_le(output, current, end); + } + else if constexpr (Correct) + { + return utf16::write_latin_correct_le(output, current, end); + } + else + { + return utf16::write_latin_le(output, current, end); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return utf16::write_utf8_pure_le(output, current, end); + } + else if constexpr (Correct) + { + return utf16::write_utf8_correct_le(output, current, end); + } + else + { + return utf16::write_utf8_le(output, current, end); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return utf16::write_utf32_pure_le(output, current, end); + } + else if constexpr (Correct) + { + return utf16::write_utf32_correct_le(output, current, end); + } + else + { + return utf16::write_utf32_le(output, current, end); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF16_BE) + { + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return utf16::write_latin_pure_be(output, current, end); + } + else if constexpr (Correct) + { + return utf16::write_latin_correct_be(output, current, end); + } + else + { + return utf16::write_latin_be(output, current, end); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return utf16::write_utf8_pure_be(output, current, end); + } + else if constexpr (Correct) + { + return utf16::write_utf8_correct_be(output, current, end); + } + else + { + return utf16::write_utf8_be(output, current, end); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return utf16::write_utf32_pure_be(output, current, end); + } + else if constexpr (Correct) + { + return utf16::write_utf32_correct_be(output, current, end); + } + else + { + return utf16::write_utf32_be(output, current, end); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF32) + { + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return utf32::write_latin_pure(output, current, end); + } + else if constexpr (Correct) + { + return utf32::write_latin_correct(output, current, end); + } + else + { + return utf32::write_latin(output, current, end); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return utf32::write_utf8_pure(output, current, end); + } + else if constexpr (Correct) + { + return utf32::write_utf8_correct(output, current, end); + } + else + { + return utf32::write_utf8(output, current, end); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return utf32::write_utf16_le_pure(output, current, end); + } + else if constexpr (Correct) + { + return utf32::write_utf16_le_correct(output, current, end); + } + else + { + return utf32::write_utf16_le(output, current, end); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return utf32::write_utf16_be_pure(output, current, end); + } + else if constexpr (Correct) + { + return utf32::write_utf16_be_correct(output, current, end); + } + else + { + return utf32::write_utf16_be(output, current, end); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + private: + template + requires ( + std::is_convertible_v> or + std::is_convertible_v::const_pointer> + ) + [[nodiscard]] constexpr static auto do_convert( + const typename output_type_of::pointer output, + const Input input + ) noexcept -> auto + { + if constexpr (InputType == CharsType::LATIN) + { + using namespace latin; + + if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return scalar::write_utf8_pure(output, input); + } + else if constexpr (Correct) + { + return scalar::write_utf8_correct(output, input); + } + else + { + return scalar::write_utf8(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return scalar::write_utf16_le_pure(output, input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_le_correct(output, input); + } + else + { + return scalar::write_utf16_le(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return scalar::write_utf16_be_pure(output, input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_be_correct(output, input); + } + else + { + return scalar::write_utf16_be(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return scalar::write_utf32_pure(output, input); + } + else if constexpr (Correct) + { + return scalar::write_utf32_correct(output, input); + } + else + { + return scalar::write_utf32(output, input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF8_CHAR) + { + using namespace utf8_char; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return scalar::write_latin_pure(output, input); + } + else if constexpr (Correct) + { + return scalar::write_latin_correct(output, input); + } + else + { + return scalar::write_latin(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF8) + { + return scalar::write_utf8(output, input); + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return scalar::write_utf16_le_pure(output, input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_le_correct(output, input); + } + else + { + return scalar::write_utf16_le(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return scalar::write_utf16_be_pure(output, input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_be_correct(output, input); + } + else + { + return scalar::write_utf16_be(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return scalar::write_utf32_pure(output, input); + } + else if constexpr (Correct) + { + return scalar::write_utf32_correct(output, input); + } + else + { + return scalar::write_utf32(output, input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF8) + { + using namespace utf8; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return scalar::write_latin_pure(output, input); + } + else if constexpr (Correct) + { + return scalar::write_latin_correct(output, input); + } + else + { + return scalar::write_latin(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR) + { + return scalar::write_utf8(output, input); + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return scalar::write_utf16_le_pure(output, input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_le_correct(output, input); + } + else + { + return scalar::write_utf16_le(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return scalar::write_utf16_be_pure(output, input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_be_correct(output, input); + } + else + { + return scalar::write_utf16_be(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return scalar::write_utf32_pure(output, input); + } + else if constexpr (Correct) + { + return scalar::write_utf32_correct(output, input); + } + else + { + return scalar::write_utf32(output, input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF16_LE) + { + using namespace utf16; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return scalar::write_latin_pure_le(output, input); + } + else if constexpr (Correct) + { + return scalar::write_latin_correct_le(output, input); + } + else + { + return scalar::write_latin_le(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return scalar::write_utf8_pure_le(output, input); + } + else if constexpr (Correct) + { + return scalar::write_utf8_correct_le(output, input); + } + else + { + return scalar::write_utf8_le(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + return scalar::write_utf16_le(output, input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return scalar::write_utf32_pure_le(output, input); + } + else if constexpr (Correct) + { + return scalar::write_utf32_correct_le(output, input); + } + else + { + return scalar::write_utf32_le(output, input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF16_BE) + { + using namespace utf16; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return scalar::write_latin_pure_be(output, input); + } + else if constexpr (Correct) + { + return scalar::write_latin_correct_be(output, input); + } + else + { + return scalar::write_latin_be(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return scalar::write_utf8_pure_be(output, input); + } + else if constexpr (Correct) + { + return scalar::write_utf8_correct_be(output, input); + } + else + { + return scalar::write_utf8_be(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + return scalar::write_utf16_be(output, input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return scalar::write_utf32_pure_be(output, input); + } + else if constexpr (Correct) + { + return scalar::write_utf32_correct_be(output, input); + } + else + { + return scalar::write_utf32_be(output, input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF32) + { + using namespace utf32; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return scalar::write_latin_pure(output, input); + } + else if constexpr (Correct) + { + return scalar::write_latin_correct(output, input); + } + else + { + return scalar::write_latin(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return scalar::write_utf8_pure(output, input); + } + else if constexpr (Correct) + { + return scalar::write_utf8_correct(output, input); + } + else + { + return scalar::write_utf8(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return scalar::write_utf16_le_pure(output, input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_le_correct(output, input); + } + else + { + return scalar::write_utf16_le(output, input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return scalar::write_utf16_be_pure(output, input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_be_correct(output, input); + } + else + { + return scalar::write_utf16_be(output, input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + public: + template + [[nodiscard]] constexpr static auto convert( + const typename output_type_of::pointer output, + const input_type_of input + ) noexcept -> auto + { + return Scalar::do_convert(output, input); + } + + template + [[nodiscard]] constexpr static auto convert( + const typename output_type_of::pointer output, + const typename input_type_of::const_pointer input + ) noexcept -> auto + { + return Scalar::do_convert(output, input); + } + + private: + template + [[nodiscard]] constexpr static auto do_convert(const Input input) noexcept -> StringType + { + if constexpr (InputType == CharsType::LATIN) + { + using namespace latin; + + if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return scalar::write_utf8_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf8_correct(input); + } + else + { + return scalar::write_utf8(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return scalar::write_utf16_le_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_le_correct(input); + } + else + { + return scalar::write_utf16_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return scalar::write_utf16_be_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_be_correct(input); + } + else + { + return scalar::write_utf16_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return scalar::write_utf32_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf32_correct(input); + } + else + { + return scalar::write_utf32(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF8_CHAR) + { + using namespace utf8_char; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return scalar::write_latin_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_latin_correct(input); + } + else + { + return scalar::write_latin(input); + } + } + else if constexpr (OutputType == CharsType::UTF8) + { + return scalar::write_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return scalar::write_utf16_le_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_le_correct(input); + } + else + { + return scalar::write_utf16_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return scalar::write_utf16_be_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_be_correct(input); + } + else + { + return scalar::write_utf16_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return scalar::write_utf32_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf32_correct(input); + } + else + { + return scalar::write_utf32(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF8) + { + using namespace utf8; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return scalar::write_latin_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_latin_correct(input); + } + else + { + return scalar::write_latin(input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR) + { + return scalar::write_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return scalar::write_utf16_le_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_le_correct(input); + } + else + { + return scalar::write_utf16_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return scalar::write_utf16_be_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_be_correct(input); + } + else + { + return scalar::write_utf16_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return scalar::write_utf32_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf32_correct(input); + } + else + { + return scalar::write_utf32(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF16_LE) + { + using namespace utf16; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return scalar::write_latin_pure_le(input); + } + else if constexpr (Correct) + { + return scalar::write_latin_correct_le(input); + } + else + { + return scalar::write_latin_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return scalar::write_utf8_pure_le(input); + } + else if constexpr (Correct) + { + return scalar::write_utf8_correct_le(input); + } + else + { + return scalar::write_utf8_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return scalar::write_utf32_pure_le(input); + } + else if constexpr (Correct) + { + return scalar::write_utf32_correct_le(input); + } + else + { + return scalar::write_utf32_le(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF16_BE) + { + using namespace utf16; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return scalar::write_latin_pure_be(input); + } + else if constexpr (Correct) + { + return scalar::write_latin_correct_be(input); + } + else + { + return scalar::write_latin_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return scalar::write_utf8_pure_be(input); + } + else if constexpr (Correct) + { + return scalar::write_utf8_correct_be(input); + } + else + { + return scalar::write_utf8_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return scalar::write_utf32_pure_be(input); + } + else if constexpr (Correct) + { + return scalar::write_utf32_correct_be(input); + } + else + { + return scalar::write_utf32_be(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF32) + { + using namespace utf32; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return scalar::write_latin_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_latin_correct(input); + } + else + { + return scalar::write_latin(input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR or OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return scalar::write_utf8_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf8_correct(input); + } + else + { + return scalar::write_utf8(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return scalar::write_utf16_le_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_le_correct(input); + } + else + { + return scalar::write_utf16_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return scalar::write_utf16_be_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_be_correct(input); + } + else + { + return scalar::write_utf16_be(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + public: + template + [[nodiscard]] constexpr static auto convert(const input_type_of input) noexcept -> result_error_input_type + { + return Scalar::do_convert(input); + } + + template + [[nodiscard]] constexpr static auto convert(const typename input_type_of::const_pointer input) noexcept -> result_error_input_type + { + return Scalar::do_convert(input); + } + + private: + template + [[nodiscard]] constexpr static auto do_convert(const Input input) noexcept -> std::basic_string::value_type> + { + if constexpr (InputType == CharsType::LATIN) + { + using namespace latin; + + if constexpr (OutputType == CharsType::UTF8_CHAR) + { + if constexpr (Pure) + { + return scalar::write_utf8_char_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf8_char_correct(input); + } + else + { + return scalar::write_utf8_char(input); + } + } + else if constexpr (OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return scalar::write_utf8_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf8_correct(input); + } + else + { + return scalar::write_utf8(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return scalar::write_utf16_le_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_le_correct(input); + } + else + { + return scalar::write_utf16_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return scalar::write_utf16_be_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_be_correct(input); + } + else + { + return scalar::write_utf16_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return scalar::write_utf32_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf32_correct(input); + } + else + { + return scalar::write_utf32(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF8_CHAR) + { + using namespace utf8_char; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return scalar::write_latin_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_latin_correct(input); + } + else + { + return scalar::write_latin(input); + } + } + else if constexpr (OutputType == CharsType::UTF8) + { + return scalar::write_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return scalar::write_utf16_le_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_le_correct(input); + } + else + { + return scalar::write_utf16_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return scalar::write_utf16_be_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_be_correct(input); + } + else + { + return scalar::write_utf16_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return scalar::write_utf32_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf32_correct(input); + } + else + { + return scalar::write_utf32(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF8) + { + using namespace utf8; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return scalar::write_latin_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_latin_correct(input); + } + else + { + return scalar::write_latin(input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR) + { + return scalar::write_utf8(input); + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return scalar::write_utf16_le_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_le_correct(input); + } + else + { + return scalar::write_utf16_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return scalar::write_utf16_be_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_be_correct(input); + } + else + { + return scalar::write_utf16_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return scalar::write_utf32_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf32_correct(input); + } + else + { + return scalar::write_utf32(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF16_LE) + { + using namespace utf16; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return scalar::write_latin_pure_le(input); + } + else if constexpr (Correct) + { + return scalar::write_latin_correct_le(input); + } + else + { + return scalar::write_latin_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR) + { + if constexpr (Pure) + { + return scalar::write_utf8_char_pure_le(input); + } + else if constexpr (Correct) + { + return scalar::write_utf8_char_correct_le(input); + } + else + { + return scalar::write_utf8_char_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return scalar::write_utf8_pure_le(input); + } + else if constexpr (Correct) + { + return scalar::write_utf8_correct_le(input); + } + else + { + return scalar::write_utf8_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + return scalar::write_utf16_le_to_be(input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return scalar::write_utf32_pure_le(input); + } + else if constexpr (Correct) + { + return scalar::write_utf32_correct_le(input); + } + else + { + return scalar::write_utf32_le(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF16_BE) + { + using namespace utf16; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return scalar::write_latin_pure_be(input); + } + else if constexpr (Correct) + { + return scalar::write_latin_correct_be(input); + } + else + { + return scalar::write_latin_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR) + { + if constexpr (Pure) + { + return scalar::write_utf8_char_pure_be(input); + } + else if constexpr (Correct) + { + return scalar::write_utf8_char_correct_be(input); + } + else + { + return scalar::write_utf8_char_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return scalar::write_utf8_pure_be(input); + } + else if constexpr (Correct) + { + return scalar::write_utf8_correct_be(input); + } + else + { + return scalar::write_utf8_be(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + return scalar::write_utf16_be_to_le(input); + } + else if constexpr (OutputType == CharsType::UTF32) + { + if constexpr (Pure) + { + return scalar::write_utf32_pure_be(input); + } + else if constexpr (Correct) + { + return scalar::write_utf32_correct_be(input); + } + else + { + return scalar::write_utf32_be(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (InputType == CharsType::UTF32) + { + using namespace utf32; + + if constexpr (OutputType == CharsType::LATIN) + { + if constexpr (Pure) + { + return scalar::write_latin_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_latin_correct(input); + } + else + { + return scalar::write_latin(input); + } + } + else if constexpr (OutputType == CharsType::UTF8_CHAR) + { + if constexpr (Pure) + { + return scalar::write_utf8_char_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf8_char_correct(input); + } + else + { + return scalar::write_utf8_char(input); + } + } + else if constexpr (OutputType == CharsType::UTF8) + { + if constexpr (Pure) + { + return scalar::write_utf8_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf8_correct(input); + } + else + { + return scalar::write_utf8(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_LE) + { + if constexpr (Pure) + { + return scalar::write_utf16_le_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_le_correct(input); + } + else + { + return scalar::write_utf16_le(input); + } + } + else if constexpr (OutputType == CharsType::UTF16_BE) + { + if constexpr (Pure) + { + return scalar::write_utf16_be_pure(input); + } + else if constexpr (Correct) + { + return scalar::write_utf16_be_correct(input); + } + else + { + return scalar::write_utf16_be(input); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + public: + template + [[nodiscard]] constexpr static auto convert(const input_type_of input) noexcept -> std::basic_string::value_type> + { + return Scalar::do_convert(input); + } + + template + [[nodiscard]] constexpr static auto convert(const typename input_type_of::const_pointer input) noexcept -> std::basic_string::value_type> + { + return Scalar::do_convert(input); + } + + private: + template + constexpr static auto do_flip( + const output_type_of::pointer output, + const Input input + ) noexcept -> void + { + using namespace utf16; + + scalar::flip(output, input); + } + + public: + constexpr static auto flip( + const output_type_of::pointer output, + const input_type_of input + ) noexcept -> void + { + return Scalar::do_flip(output, input); + } + + constexpr static auto flip( + const output_type_of::pointer output, + const input_type_of::const_pointer input + ) noexcept -> void + { + return Scalar::do_flip(output, input); + } + + private: + template + [[nodiscard]] constexpr static auto do_flip(const Input input) noexcept -> StringType + { + using namespace utf16; + + return scalar::flip(input); + } + + public: + template + [[nodiscard]] constexpr static auto flip(const input_type_of input) noexcept -> StringType + { + return Scalar::do_flip(input); + } + + template + [[nodiscard]] constexpr static auto flip(const input_type_of::const_pointer input) noexcept -> StringType + { + return Scalar::do_flip(input); + } + + private: + template + [[nodiscard]] constexpr static auto do_flip(const Input input) noexcept -> std::basic_string::value_type> + { + using namespace utf16; + + return scalar::flip(input); + } + + public: + [[nodiscard]] constexpr static auto flip(const input_type_of input) noexcept -> std::basic_string::value_type> + { + return Scalar::do_flip(input); + } + + [[nodiscard]] constexpr static auto flip(const input_type_of::const_pointer input) noexcept -> std::basic_string::value_type> + { + return Scalar::do_flip(input); + } + }; +} diff --git a/src/chars/scalar.ixx b/src/chars/scalar.ixx deleted file mode 100644 index 4a350df8..00000000 --- a/src/chars/scalar.ixx +++ /dev/null @@ -1,79 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.chars:scalar; - -import std; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -import :encoding; -export import :scalar.ascii; -export import :scalar.utf8; -export import :scalar.utf16; -export import :scalar.utf32; - -#else -#include -#include -#include -#include -#include - -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE -#endif - -// ReSharper disable once CppRedundantNamespaceDefinition -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::chars) -{ - template<> - class Encoding<"scalar"> - { - public: - [[nodiscard]] constexpr static auto encoding_of(const std::span input) noexcept -> EncodingType - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - if (const auto bom = bom_of(input); bom != EncodingType::UNKNOWN) - { - return bom; - } - - auto all_possible = std::to_underlying(EncodingType::UNKNOWN); - if (Scalar<"utf8">::validate(input)) - { - all_possible |= std::to_underlying(EncodingType::UTF8); - } - - if ((input.size() % 2) == 0 and - Scalar<"utf16">::validate({GAL_PROMETHEUS_SEMANTIC_UNRESTRICTED_CHAR_POINTER_CAST(Scalar<"utf16">::char_type, input.data()), input.size() / 2})) - { - all_possible |= std::to_underlying(EncodingType::UTF16_LE); - } - - if ((input.size() % 4) == 0 and - Scalar<"utf32">::validate({GAL_PROMETHEUS_SEMANTIC_UNRESTRICTED_CHAR_POINTER_CAST(Scalar<"utf32">::char_type, input.data()), input.size() / 4})) - { - all_possible |= std::to_underlying(EncodingType::UTF32_LE); - } - - return static_cast(all_possible); - } - - [[nodiscard]] constexpr static auto encoding_of(const std::span input) noexcept -> EncodingType - { - static_assert(sizeof(char) == sizeof(char8_t)); - - const auto* char8_string = GAL_PROMETHEUS_SEMANTIC_UNRESTRICTED_CHAR_POINTER_CAST(char8_t, input.data()); - return encoding_of({char8_string, input.size()}); - } - }; -} diff --git a/src/chars/scalar_ascii.ixx b/src/chars/scalar_ascii.ixx deleted file mode 100644 index b7014f94..00000000 --- a/src/chars/scalar_ascii.ixx +++ /dev/null @@ -1,328 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.chars:scalar.ascii; - -import std; -import gal.prometheus.meta; -import gal.prometheus.memory; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -import :encoding; - -#else -#include -#include - -#include -#include -#include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -#endif - -// ReSharper disable once CppRedundantNamespaceDefinition -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::chars) -{ - template<> - class Scalar<"ascii"> - { - public: - constexpr static auto input_category = CharsCategory::ASCII; - using input_type = chars::input_type; - using char_type = input_type::value_type; - using pointer_type = input_type::const_pointer; - using size_type = input_type::size_type; - - template - [[nodiscard]] constexpr static auto validate(const input_type input) noexcept -> std::conditional_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - for (; it_input_current + 16 <= it_input_end; it_input_current += 16) - { - const auto v1 = memory::unaligned_load(it_input_current + 0); - const auto v2 = memory::unaligned_load(it_input_current + sizeof(std::uint64_t)); - - if (const auto value = v1 | v2; - (value & 0x8080'8080'8080'8080) != 0) { break; } - } - - if (const auto it = - std::ranges::find_if( - it_input_current, - it_input_end, - [](const auto byte) noexcept { return byte >= 0b1000'0000; }); - it != it_input_end) - { - if constexpr (ReturnResultType) - { - return result_type{.error = ErrorCode::TOO_LARGE, .count = static_cast(std::ranges::distance(it, it_input_begin))}; - } - else - { - return false; - } - } - - if constexpr (ReturnResultType) - { - return result_type{.error = ErrorCode::NONE, .count = input_length}; - } - else - { - return true; - } - } - - template - [[nodiscard]] constexpr static auto validate(const pointer_type input) noexcept -> std::conditional_t - { - return validate({input, std::char_traits::length(input)}); - } - - // note: we are not BOM aware - template - [[nodiscard]] constexpr static auto length(const input_type input) noexcept -> size_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - if constexpr (OutputCategory == CharsCategory::ASCII) { return input.size(); } // NOLINT(bugprone-branch-clone) - else if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) - { - return std::transform_reduce( - input.begin(), - input.end(), - input.size(), - std::plus<>{}, - [](const auto byte) noexcept { return +(byte >> 7); }); - } - else if constexpr (OutputCategory == CharsCategory::UTF16_LE or OutputCategory == CharsCategory::UTF16_BE or OutputCategory == CharsCategory::UTF16) { return input.size(); } - else if constexpr (OutputCategory == CharsCategory::UTF32) { return input.size(); } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - // note: we are not BOM aware - template - [[nodiscard]] constexpr static auto length(const pointer_type input) noexcept -> size_type - { - return length({input, std::char_traits::length(input)}); - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert( - const input_type input, - typename output_type::pointer output - ) noexcept -> std::conditional_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); - if constexpr (ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(validate(input)); - } - - using output_pointer_type = typename output_type::pointer; - using output_char_type = typename output_type::value_type; - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - const output_pointer_type it_output_begin = output; - output_pointer_type it_output_current = it_output_begin; - - if constexpr (OutputCategory == CharsCategory::ASCII) - { - std::memcpy(it_output_current, it_input_current, input_length * sizeof(char_type)); - it_input_current += input_length; - it_output_current += input_length; - } - else if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) - { - for (; it_input_current < it_input_end; ++it_input_current) - { - if constexpr (CheckNextBlock) - { - // if it is safe to read 16 more bytes, check that they are ascii - if (it_input_current + 16 <= it_input_end) - { - const auto v1 = memory::unaligned_load(it_input_current + 0); - const auto v2 = memory::unaligned_load(it_input_current + sizeof(std::uint64_t)); - - if (const auto value = v1 | v2; - (value & 0x8080'8080'8080'8080) == 0) - { - std::ranges::transform( - it_input_current, - it_input_current + 16, - it_output_current, - [](const auto byte) noexcept { return static_cast(byte); }); - - // 15 more step, see `for (; it_input_current < it_input_end; ++it_input_current)` - it_input_current += 15; - it_output_current += 16; - continue; - } - } - } - - if (const auto byte = *it_input_current; - (byte & 0x80) == 0) - { - *(it_output_current + 0) = static_cast(byte); - it_output_current += 1; - } - else - { - *(it_output_current + 0) = static_cast((byte >> 6) | 0b1100'0000); - *(it_output_current + 1) = static_cast((byte & 0b0011'1111) | 0b1000'0000); - it_output_current += 2; - } - } - } - else if constexpr ( - OutputCategory == CharsCategory::UTF16_LE or - OutputCategory == CharsCategory::UTF16_BE or - // OutputCategory == CharsCategory::UTF16 or - OutputCategory == CharsCategory::UTF32 - ) - { - std::ranges::transform( - it_input_current, - it_input_end, - it_output_current, - [](const auto byte) noexcept - { - if constexpr ( - OutputCategory == CharsCategory::UTF32 or - ((OutputCategory == CharsCategory::UTF16_LE) == (std::endian::native == std::endian::little)) - ) { return static_cast(byte); } - else { return static_cast(std::byteswap(static_cast(byte))); } - }); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE("Unknown or unsupported `OutputCategory` (we don't know the `endian` by UTF16, so it's not allowed to use it here)."); } - - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { return static_cast(it_output_current - it_output_begin); } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::NONE, .count = static_cast(it_input_current - it_input_begin)}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert( - const pointer_type input, - typename output_type::pointer output - ) noexcept -> std::conditional_t - { - return convert({input, std::char_traits::length(input)}, output); - } - - template< - typename StringType, - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - requires requires(StringType& string) - { - string.resize(std::declval()); - { - string.data() - } -> std::convertible_to::pointer>; - } - [[nodiscard]] constexpr static auto convert(const input_type input) noexcept -> StringType - { - StringType result{}; - result.resize(length(input)); - - (void)convert(input, result.data()); - return result; - } - - template< - typename StringType, - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - requires requires(StringType& string) - { - string.resize(std::declval()); - { - string.data() - } -> std::convertible_to::pointer>; - } - [[nodiscard]] constexpr static auto convert(const pointer_type input) noexcept -> StringType - { - StringType result{}; - result.resize(length(input)); - - return convert({input, std::char_traits::length(input)}, result.data()); - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert(const input_type input) noexcept -> std::basic_string::value_type> - { - std::basic_string::value_type> result{}; - result.resize(length(input)); - - (void)convert(input, result.data()); - return result; - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert(const pointer_type input) noexcept -> std::basic_string::value_type> - { - std::basic_string::value_type> result{}; - result.resize(length(input)); - - return convert({input, std::char_traits::length(input)}, result.data()); - } - }; - - template<> - struct scalar_processor_of - { - using type = Scalar<"ascii">; - }; -} diff --git a/src/chars/scalar_utf16.ixx b/src/chars/scalar_utf16.ixx deleted file mode 100644 index 4e7e7a58..00000000 --- a/src/chars/scalar_utf16.ixx +++ /dev/null @@ -1,705 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.chars:scalar.utf16; - -import std; -import gal.prometheus.meta; -import gal.prometheus.memory; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -import :encoding; - -#else -#include -#include - -#include -#include -#include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -#endif - -// ReSharper disable once CppRedundantNamespaceDefinition -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::chars) -{ - template<> - class Scalar<"utf16"> - { - public: - constexpr static auto input_category = CharsCategory::UTF16; - using input_type = chars::input_type; - using char_type = input_type::value_type; - using pointer_type = input_type::const_pointer; - using size_type = input_type::size_type; - - template - [[nodiscard]] constexpr static auto validate(const input_type input) noexcept -> std::conditional_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - while (it_input_current != it_input_end) - { - if (const auto word = [it_input_current]() noexcept -> auto - { - if constexpr (Endian == std::endian::native) { return *it_input_current; } - else { return std::byteswap(*it_input_current); } - }(); - (word & 0xf800) == 0xd800) - { - const auto count_if_error = static_cast(it_input_current - it_input_end); - - if (it_input_current + 1 == it_input_end) - { - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::SURROGATE, .count = count_if_error}; - } - else - { - return false; - } - } - - if (const auto diff = word - 0xd800; - diff > 0x3ff) - { - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::SURROGATE, .count = count_if_error}; - } - else - { - return false; - } - } - - const auto next_word = [it_input_current]() noexcept -> auto - { - if constexpr (Endian == std::endian::native) { return *(it_input_current + 1); } - else { return std::byteswap(*(it_input_current + 1)); } - }(); - - if (const auto diff = next_word - 0xdc00; - diff > 0x3ff) - { - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::SURROGATE, .count = count_if_error}; - } - else - { - return false; - } - } - - it_input_current += 2; - } - else { it_input_current += 1; } - } - - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::NONE, .count = input_length}; - } - else - { - return true; - } - } - - template - [[nodiscard]] constexpr static auto validate(const pointer_type input) noexcept -> std::conditional_t - { - return validate({input, std::char_traits::length(input)}); - } - - // note: we are not BOM aware - template - [[nodiscard]] constexpr static auto length(const input_type input) noexcept -> size_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - if constexpr (OutputCategory == CharsCategory::ASCII) { return input.size(); } // NOLINT(bugprone-branch-clone) - else if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) - { - return std::transform_reduce( - input.begin(), - input.end(), - static_cast(0), - std::plus<>{}, - [](const auto word) noexcept - { - const auto native_word = [word]() noexcept - { - if constexpr (Endian != std::endian::native) { return std::byteswap(word); } - else { return word; } - }(); - - return 1 // ascii - + - (native_word > 0x7f) // non-ASCII is at least 2 bytes, surrogates are 2*2 == 4 bytes - + - ((native_word > 0x7ff && native_word <= 0xd7ff) || (native_word >= 0xe000)) // three-byte - ; - }); - } - else if constexpr (OutputCategory == CharsCategory::UTF16_LE or OutputCategory == CharsCategory::UTF16_BE or OutputCategory == CharsCategory::UTF16) - { - return input.size(); - } - else if constexpr (OutputCategory == CharsCategory::UTF32) - { - return std::transform_reduce( - input.begin(), - input.end(), - static_cast(0), - std::plus<>{}, - [](const auto word) noexcept - { - const auto native_word = [word]() noexcept - { - if constexpr (Endian != std::endian::native) { return std::byteswap(word); } - else { return word; } - }(); - - return +((native_word & 0xfc00) != 0xdc00); - }); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - // note: we are not BOM aware - template - [[nodiscard]] constexpr static auto length(const pointer_type input) noexcept -> size_type - { - return length({input, std::char_traits::length(input)}); - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - std::endian Endian = std::endian::native, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert( - const input_type input, - typename output_type::pointer output - ) noexcept -> std::conditional_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); - if constexpr (ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(validate(input)); - } - - using output_pointer_type = typename output_type::pointer; - using output_char_type = typename output_type::value_type; - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - const output_pointer_type it_output_begin = output; - output_pointer_type it_output_current = it_output_begin; - - if constexpr (OutputCategory == CharsCategory::ASCII) - { - for (; it_input_current < it_input_end; ++it_input_current) - { - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - if constexpr (CheckNextBlock) - { - struct pack - { - std::uint64_t v1; - std::uint64_t v2; - std::uint64_t v3; - std::uint64_t v4; - }; - - const auto to_native_word = [](const pack word) noexcept -> pack - { - if constexpr (Endian != std::endian::native) - { - const auto [v1, v2, v3, v4] = word; - return { - .v1 = (v1 >> 8) | (v1 << (64 - 8)), - .v2 = (v2 >> 8) | (v2 << (64 - 8)), - .v3 = (v3 >> 8) | (v3 << (64 - 8)), - .v4 = (v4 >> 8) | (v4 << (64 - 8))}; - } - else { return word; } - }; - - // if it is safe to read 32 more bytes, check that they are ascii - if (it_input_current + 16 <= it_input_end) - { - const auto [v1, v2, v3, v4] = to_native_word( - { - .v1 = memory::unaligned_load(it_input_current + 0), - .v2 = memory::unaligned_load(it_input_current + 4), - .v3 = memory::unaligned_load(it_input_current + 8), - .v4 = memory::unaligned_load(it_input_current + 12) - } - ); - - if (const auto value = (v1 | v2 | v3 | v4); - (value & 0xff00'ff00'ff00'ff00) == 0) - { - std::ranges::transform( - it_input_current, - it_input_current + 16, - it_output_current, - [](const auto word) noexcept - { - if constexpr (Endian != std::endian::native) - { - return static_cast(std::byteswap(word)); - } - else { return static_cast(word); } - }); - - // 15 more steps, see `for (; it_input_current < it_input_end; ++it_input_current)` - it_input_current += 15; - it_output_current += 16; - continue; - } - } - } - - const auto word = [w = *it_input_current]() noexcept - { - if constexpr (Endian != std::endian::native) { return std::byteswap(w); } - else { return w; } - }(); - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if ((word & 0xff00) != 0) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::TOO_LARGE, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - } - - *(it_output_current + 0) = static_cast(word & 0xff); - it_output_current += 1; - } - } - else if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) - { - for (; it_input_current < it_input_end; ++it_input_current) - { - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - if constexpr (CheckNextBlock) - { - const auto to_native_word = [](const auto word) noexcept -> auto - { - if constexpr (Endian != std::endian::native) { return (word >> 8) | (word << (64 - 8)); } - else { return word; } - }; - - // if it is safe to read 8 more bytes, check that they are ascii - if (it_input_current + 4 <= it_input_end) - { - if (const auto value = to_native_word(memory::unaligned_load(it_input_current)); - (value & 0xff80'ff80'ff80'ff80) == 0) - { - std::ranges::transform( - it_input_current, - it_input_current + 4, - it_output_current, - [](const auto word) noexcept - { - if constexpr (Endian != std::endian::native) - { - return static_cast(std::byteswap(word)); - } - else { return static_cast(word); } - }); - - // 3 more step, see `for (; it_input_current < it_input_end; ++it_input_current)` - it_input_current += 3; - it_output_current += 4; - continue; - } - } - } - - if (const auto word = [w = *it_input_current]() noexcept - { - if constexpr (Endian != std::endian::native) { return std::byteswap(w); } - else { return w; } - }(); - (word & 0xff80) == 0) - { - // 1-byte utf8 - *(it_output_current + 0) = static_cast(word); - it_output_current += 1; - } - else if ((word & 0xf800) == 0) - { - // 2-bytes utf8 - // 0b110?'???? 0b10??'???? - *(it_output_current + 0) = static_cast((word >> 6) | 0b1100'0000); - *(it_output_current + 1) = static_cast((word & 0b0011'1111) | 0b1000'0000); - it_output_current += 2; - } - else if ((word & 0xf800) != 0xd800) - { - // 3-bytes utf8 - // 0b1110'???? 0b10??'???? 0b10??'???? - *(it_output_current + 0) = static_cast((word >> 12) | 0b1110'0000); - *(it_output_current + 1) = static_cast(((word >> 6) & 0b0011'1111) | 0b1000'0000); - *(it_output_current + 2) = static_cast((word & 0b0011'1111) | 0b1000'0000); - it_output_current += 3; - } - else - { - // 4-bytes utf8 - // must be a surrogate pair - const auto diff = word - 0xd800; - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if (diff > 0x3ff) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::SURROGATE, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - } - - // minimal bound checking - if (it_input_current + 1 >= it_input_end) - { - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::SURROGATE, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - const auto next_word = [w = *(it_input_current + 1)]() noexcept - { - if constexpr (Endian != std::endian::native) { return std::byteswap(w); } - else { return w; } - }(); - const auto next_diff = next_word - 0xdc00; - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if (next_diff > 0x3ff) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::SURROGATE, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - } - - const auto value = (diff << 10) + next_diff + 0x1'0000; - - // 0b1111'0??? 0b10??'???? 0b10??'???? 0b10??'???? - *(it_output_current + 0) = static_cast((value >> 18) | 0b1111'0000); - *(it_output_current + 1) = static_cast(((value >> 12) & 0b0011'1111) | 0b1000'0000); - *(it_output_current + 2) = static_cast(((value >> 6) & 0b0011'1111) | 0b1000'0000); - *(it_output_current + 3) = static_cast((value & 0b0011'1111) | 0b1000'0000); - - // one more step, see `for (; it_input_current < it_input_end; ++it_input_current)` - it_input_current += 1; - it_output_current += 4; - } - } - } - else if constexpr (OutputCategory == CharsCategory::UTF16_LE or OutputCategory == CharsCategory::UTF16_BE or OutputCategory == CharsCategory::UTF16) - { - if constexpr ((OutputCategory == CharsCategory::UTF16) or ((Endian == std::endian::little) == (OutputCategory == CharsCategory::UTF16_LE))) - { - std::memcpy(it_output_current, it_input_current, input_length * sizeof(char_type)); - } - else - { - flip_endian(input, output); - } - - it_input_current += input_length; - it_output_current += input_length; - } - else if constexpr (OutputCategory == CharsCategory::UTF32) - { - for (; it_input_current < it_input_end; ++it_input_current) - { - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - if (const auto word = [w = *(it_input_current + 0)]() noexcept - { - if constexpr (Endian != std::endian::native) { return std::byteswap(w); } - else { return w; } - }(); - (word & 0xf800) != 0xd800) - { - // no surrogate pair, extend 16-bit word to 32-bit word - *(it_output_current + 0) = static_cast(word); - it_output_current += 1; - } - else - { - // must be a surrogate pair - const auto diff = word - 0xd800; - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if (diff > 0x3ff) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::SURROGATE, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - } - - // minimal bound checking - if (it_input_current + 1 >= it_input_end) - { - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::SURROGATE, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - const auto next_word = [w = *(it_input_current + 1)]() noexcept - { - if constexpr (Endian != std::endian::native) { return std::byteswap(w); } - else { return w; } - }(); - const auto next_diff = next_word - 0xdc00; - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if (next_diff > 0x3ff) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::SURROGATE, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - } - - const auto value = (diff << 10) + next_diff + 0x1'0000; - - *(it_output_current + 0) = static_cast(value); - - // one more step, see `for (; it_input_current < it_input_end; ++it_input_current)` - it_input_current += 1; - it_output_current += 1; - } - } - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { return static_cast(it_output_current - it_output_begin); } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::NONE, .count = static_cast(it_input_current - it_input_begin)}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - std::endian Endian = std::endian::native, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert( - const pointer_type input, - typename output_type::pointer output - ) noexcept -> std::conditional_t - { - return convert({input, std::char_traits::length(input)}, output); - } - - template< - typename StringType, - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - std::endian Endian = std::endian::native, - bool CheckNextBlock = true - > - requires requires(StringType& string) - { - string.resize(std::declval()); - { - string.data() - } -> std::convertible_to::pointer>; - } - [[nodiscard]] constexpr static auto convert(const input_type input) noexcept -> StringType - { - StringType result{}; - result.resize(length(input)); - - (void)convert(input, result.data()); - return result; - } - - template< - typename StringType, - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - std::endian Endian = std::endian::native, - bool CheckNextBlock = true - > - requires requires(StringType& string) - { - string.resize(std::declval()); - { - string.data() - } -> std::convertible_to::pointer>; - } - [[nodiscard]] constexpr static auto convert(const pointer_type input) noexcept -> StringType - { - StringType result{}; - result.resize(length(input)); - - return convert({input, std::char_traits::length(input)}, result.data()); - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - std::endian Endian = std::endian::native, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert(const input_type input) noexcept -> std::basic_string::value_type> - { - std::basic_string::value_type> result{}; - result.resize(length(input)); - - (void)convert(input, result.data()); - return result; - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - std::endian Endian = std::endian::native, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert(const pointer_type input) noexcept -> std::basic_string::value_type> - { - std::basic_string::value_type> result{}; - result.resize(length(input)); - - return convert({input, std::char_traits::length(input)}, result.data()); - } - - template - [[nodiscard]] constexpr auto code_points(const input_type input) const noexcept -> std::size_t - { - (void)this; - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - return std::ranges::count_if( - input, - [](auto word) noexcept -> bool - { - if constexpr (Endian != std::endian::native) { word = std::byteswap(word); } - - return (word & 0xfc00) != 0xdc00; - }); - } - - constexpr auto static flip_endian(const input_type input, const output_type::pointer output) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); - - std::ranges::transform( - input, - output, - [](const auto word) noexcept { return static_cast::value_type>(std::byteswap(word)); }); - } - - template - requires requires(StringType& string) - { - string.resize(std::declval()); - { - string.data() - } -> std::convertible_to::pointer>; - } - [[nodiscard]] constexpr static auto flip_endian(const input_type input) noexcept -> StringType - { - StringType result{}; - result.resize(length(input)); - - flip_endian(input, result.data()); - return result; - } - - [[nodiscard]] constexpr static auto flip_endian(const input_type input) noexcept -> std::basic_string::value_type> - { - std::basic_string::value_type> result{}; - result.resize(length(input)); - - flip_endian(input, result.data()); - return result; - } - }; - - template<> - struct scalar_processor_of - { - using type = Scalar<"utf16">; - }; - - template<> - struct scalar_processor_of : scalar_processor_of {}; - - template<> - struct scalar_processor_of : scalar_processor_of {}; -} // namespace gal::prometheus::chars diff --git a/src/chars/scalar_utf32.ixx b/src/chars/scalar_utf32.ixx deleted file mode 100644 index 8945aecd..00000000 --- a/src/chars/scalar_utf32.ixx +++ /dev/null @@ -1,489 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.chars:scalar.utf32; - -import std; -import gal.prometheus.meta; -import gal.prometheus.memory; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -import :encoding; - -#else -#include -#include - -#include -#include -#include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -#endif - -// ReSharper disable once CppRedundantNamespaceDefinition -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::chars) -{ - template<> - class Scalar<"utf32"> - { - public: - constexpr static auto input_category = CharsCategory::UTF32; - using input_type = chars::input_type; - using char_type = input_type::value_type; - using pointer_type = input_type::const_pointer; - using size_type = input_type::size_type; - - template - [[nodiscard]] constexpr static auto validate(const input_type input) noexcept -> std::conditional_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - while (it_input_current != it_input_end) - { - const auto count_if_error = static_cast(it_input_current - it_input_begin); - - if (const auto word = *it_input_current; - word > 0x10'ffff) - { - if constexpr (ReturnResultType) - { - return result_type{.error = ErrorCode::TOO_LARGE, .count = count_if_error}; - } - else - { - return false; - } - } - else if (word >= 0xd800 and word <= 0xdfff) - { - if constexpr (ReturnResultType) - { - return result_type{.error = ErrorCode::SURROGATE, .count = count_if_error}; - } - else - { - return false; - } - } - - it_input_current += 1; - } - - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::NONE, .count = input_length}; - } - else - { - return true; - } - } - - template - [[nodiscard]] constexpr static auto validate(const pointer_type input) noexcept -> std::conditional_t - { - return validate({input, std::char_traits::length(input)}); - } - - // note: we are not BOM aware - template - [[nodiscard]] constexpr static auto length(const input_type input) noexcept -> size_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - if constexpr (OutputCategory == CharsCategory::ASCII) { return input.size(); } // NOLINT(bugprone-branch-clone) - else if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) - { - return std::transform_reduce( - input.begin(), - input.end(), - static_cast(0), - std::plus<>{}, - [](const auto data) noexcept - { - return 1 // ascii - + - (data > 0x7f) // two-byte - + - (data > 0x7ff) // three-byte - + - (data > 0xffff) // four-byte - ; - }); - } - else if constexpr (OutputCategory == CharsCategory::UTF16_LE or OutputCategory == CharsCategory::UTF16_BE or OutputCategory == CharsCategory::UTF16) - { - return std::transform_reduce( - input.begin(), - input.end(), - static_cast(0), - std::plus<>{}, - [](const auto data) noexcept - { - return 1 // non-surrogate word - + - (data > 0xffff) // surrogate pair - ; - }); - } - else if constexpr (OutputCategory == CharsCategory::UTF32) { return input.size(); } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - // note: we are not BOM aware - template - [[nodiscard]] constexpr static auto length(const pointer_type input) noexcept -> size_type - { - return length({input, std::char_traits::length(input)}); - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert( - const input_type input, - typename output_type::pointer output - ) noexcept -> std::conditional_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); - if constexpr (ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(validate(input)); - } - - using output_pointer_type = typename output_type::pointer; - using output_char_type = typename output_type::value_type; - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - const output_pointer_type it_output_begin = output; - output_pointer_type it_output_current = it_output_begin; - - if constexpr (OutputCategory == CharsCategory::ASCII) - { - for (; it_input_current < it_input_end; ++it_input_current) - { - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - if constexpr (CheckNextBlock) - { - // try to convert the next block of 2 ASCII characters - // if it is safe to read 8 more bytes, check that they are ascii - if (it_input_current + 2 <= it_input_end) - { - if (const auto value = memory::unaligned_load(it_input_current); - (value & 0xffff'ff00'ffff'ff00) == 0) - { - *(it_output_current + 0) = static_cast(*(it_input_current + 0)); - *(it_output_current + 1) = static_cast(*(it_input_current + 1)); - - // one more step, see `for (; it_input_current < it_input_end; ++it_input_current)` - it_input_current += 1; - it_output_current += 2; - continue; - } - } - } - - const auto word = *it_input_current; - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if ((word & 0xffff'ff00) != 0) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::TOO_LARGE, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - } - - *(it_output_current + 0) = static_cast(word & 0xff); - it_output_current += 1; - } - } - else if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) - { - for (; it_input_current < it_input_end; ++it_input_current) - { - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - if constexpr (CheckNextBlock) - { - // try to convert the next block of 2 ASCII characters - // if it is safe to read 8 more bytes, check that they are ascii - if (it_input_current + 2 <= it_input_end) - { - if (const auto value = memory::unaligned_load(it_input_current); - (value & 0xffff'ff80'ffff'ff80) == 0) - { - *(it_output_current + 0) = static_cast(*(it_input_current + 0)); - *(it_output_current + 1) = static_cast(*(it_input_current + 1)); - - // one more step, see `for (; it_input_current < it_input_end; ++it_input_current)` - it_input_current += 1; - it_output_current += 2; - continue; - } - } - } - - if (const auto word = *it_input_current; - (word & 0xffff'ff80) == 0) - { - // 1-byte ascii - *(it_output_current + 0) = static_cast(word); - it_output_current += 1; - } - else if ((word & 0xffff'f800) == 0) - { - // 2-bytes utf8 - // 0b110?'???? 0b10??'???? - *(it_output_current + 0) = static_cast((word >> 6) | 0b1100'0000); - *(it_output_current + 1) = static_cast((word & 0b0011'1111) | 0b1000'0000); - it_output_current += 2; - } - else if ((word & 0xffff'0000) == 0) - { - // 3-bytes utf8 - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if (word >= 0xd800 and word <= 0xdfff) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::SURROGATE, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - } - // 0b1110'???? 0b10??'???? 0b10??'???? - *(it_output_current + 0) = static_cast((word >> 12) | 0b1110'0000); - *(it_output_current + 1) = static_cast(((word >> 6) & 0b0011'1111) | 0b1000'0000); - *(it_output_current + 2) = static_cast((word & 0b0011'1111) | 0b1000'0000); - it_output_current += 3; - } - else - { - // 4-bytes utf8 - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if (word > 0x0010'ffff) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::TOO_LARGE, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - } - // 0b1111'0??? 0b10??'???? 0b10??'???? 0b10??'???? - *(it_output_current + 0) = static_cast((word >> 18) | 0b1111'0000); - *(it_output_current + 1) = static_cast(((word >> 12) & 0b0011'1111) | 0b1000'0000); - *(it_output_current + 2) = static_cast(((word >> 6) & 0b0011'1111) | 0b1000'0000); - *(it_output_current + 3) = static_cast((word & 0b0011'1111) | 0b1000'0000); - it_output_current += 4; - } - } - } - else if constexpr (OutputCategory == CharsCategory::UTF16_LE or OutputCategory == CharsCategory::UTF16_BE) - { - for (; it_input_current < it_input_end; ++it_input_current) - { - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - if (const auto word = *it_input_current; - (word & 0xffff'0000) == 0) - { - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if (word >= 0xd800 and word <= 0xdfff) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::SURROGATE, .count = length_if_error}; - } - } - } - const auto real_word = [w = static_cast(word)]() noexcept - { - if constexpr ((OutputCategory == CharsCategory::UTF16_LE) != (std::endian::native == std::endian::little)) - { - return std::byteswap(w); - } - else { return w; } - }(); - - *(it_output_current + 0) = static_cast(real_word); - it_output_current += 1; - } - else - { - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if (word > 0x0010'ffff) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::TOO_LARGE, .count = length_if_error}; - } - } - const auto [high_surrogate, low_surrogate] = [real_word = word - 0x0001'0000]() noexcept - { - const auto high = 0xd800 + (real_word >> 10); - const auto low = 0xdc00 + (real_word & 0x3ff); - if constexpr ((OutputCategory == CharsCategory::UTF16_LE) != (std::endian::native == std::endian::little)) - { - return std::make_pair(std::byteswap(high), std::byteswap(low)); - } - else { return std::make_pair(high, low); } - }(); - - *(it_output_current + 0) = static_cast(high_surrogate); - *(it_output_current + 1) = static_cast(low_surrogate); - it_output_current += 2; - } - } - } - } - else if constexpr (OutputCategory == CharsCategory::UTF32) - { - std::memcpy(it_output_current, it_input_current, input_length * sizeof(char_type)); - it_input_current += input_length; - it_output_current += input_length; - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE("Unknown or unsupported `OutputCategory` (we don't know the `endian` by UTF16, so it's not allowed to use it here)."); - } - - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { return static_cast(it_output_current - it_output_begin); } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::NONE, .count = static_cast(it_input_current - it_input_begin)}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert( - const pointer_type input, - typename output_type::pointer output - ) noexcept -> std::conditional_t - { - return convert({input, std::char_traits::length(input)}, output); - } - - template< - typename StringType, - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - requires requires(StringType& string) - { - string.resize(std::declval()); - { - string.data() - } -> std::convertible_to::pointer>; - } - [[nodiscard]] constexpr static auto convert(const input_type input) noexcept -> StringType - { - StringType result{}; - result.resize(length(input)); - - (void)convert(input, result.data()); - return result; - } - - template< - typename StringType, - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - requires requires(StringType& string) - { - string.resize(std::declval()); - { - string.data() - } -> std::convertible_to::pointer>; - } - [[nodiscard]] constexpr static auto convert(const pointer_type input) noexcept -> StringType - { - StringType result{}; - result.resize(length(input)); - - return convert({input, std::char_traits::length(input)}, result.data()); - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert(const input_type input) noexcept -> std::basic_string::value_type> - { - std::basic_string::value_type> result{}; - result.resize(length(input)); - - (void)convert(input, result.data()); - return result; - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert(const pointer_type input) noexcept -> std::basic_string::value_type> - { - std::basic_string::value_type> result{}; - result.resize(length(input)); - - return convert({input, std::char_traits::length(input)}, result.data()); - } - }; - - template<> - struct scalar_processor_of - { - using type = Scalar<"utf32">; - }; -} // namespace gal::prometheus::chars diff --git a/src/chars/scalar_utf8.ixx b/src/chars/scalar_utf8.ixx deleted file mode 100644 index cc3eca95..00000000 --- a/src/chars/scalar_utf8.ixx +++ /dev/null @@ -1,953 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#pragma once - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.chars:scalar.utf8; - -import std; -import gal.prometheus.meta; -import gal.prometheus.memory; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -import :encoding; - -#else -#include -#include - -#include -#include -#include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -#endif - -namespace gal::prometheus::chars -{ - namespace scalar_utf8_detail - { - template - class ScalarUtf8Base - { - public: - constexpr static auto input_category = InputCategory; - using input_type = chars::input_type; - using char_type = typename input_type::value_type; - using pointer_type = typename input_type::const_pointer; - using size_type = typename input_type::size_type; - - template - [[nodiscard]] constexpr static auto validate(const input_type input) noexcept -> std::conditional_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - while (it_input_current != it_input_end) - { - for (; it_input_current + 16 <= it_input_end; it_input_current += 16) - { - const auto v1 = memory::unaligned_load(it_input_current + 0); - const auto v2 = memory::unaligned_load(it_input_current + sizeof(std::uint64_t)); - - if (const auto value = v1 | v2; - (value & 0x8080'8080'8080'8080) != 0) { break; } - } - - if (const auto it = - std::ranges::find_if( - it_input_current, - it_input_end, - [](const auto byte) noexcept { return static_cast(byte) >= 0b1000'0000; }); - it == it_input_end) - { - if constexpr (ReturnResultType) - { - return result_type{.error = ErrorCode::NONE, .count = input_length}; - } - else - { - return true; - } - } - else { it_input_current = it; } - - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - if (const auto byte = static_cast(*it_input_current); - (byte & 0b1110'0000) == 0b1100'0000) - { - if (it_input_current + 2 > it_input_end) - { - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::TOO_SHORT, .count = length_if_error}; - } - else - { - return false; - } - } - - const auto next_byte = *(it_input_current + 1); - - if ((next_byte & 0b1100'0000) != 0b1000'0000) - { - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::TOO_SHORT, .count = length_if_error}; - } - else - { - return false; - } - } - // range check - if (const auto code_point = (byte & 0b0001'1111) << 6 | (next_byte & 0b0011'1111); - (code_point < 0x80) or (0x7ff < code_point)) - { - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::OVERLONG, .count = length_if_error}; - } - else - { - return false; - } - } - - it_input_current += 2; - } - else if ((byte & 0b1111'0000) == 0b1110'0000) - { - if (it_input_current + 3 > it_input_end) - { - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::TOO_SHORT, .count = length_if_error}; - } - else - { - return false; - } - } - - const auto next_byte_1 = *(it_input_current + 1); - const auto next_byte_2 = *(it_input_current + 2); - - if (((next_byte_1 & 0b1100'0000) != 0b1000'0000) or ((next_byte_2 & 0b1100'0000) != 0b1000'0000)) - { - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::TOO_SHORT, .count = length_if_error}; - } - else - { - return false; - } - } - // range check - const auto code_point = - (byte & 0b0000'1111) << 12 | - (next_byte_1 & 0b0011'1111) << 6 | - (next_byte_2 & 0b0011'1111); - if ((code_point < 0x800) or (0xffff < code_point)) - { - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::OVERLONG, .count = length_if_error}; - } - else - { - return false; - } - } - if (0xd7ff < code_point and code_point < 0xe000) - { - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::SURROGATE, .count = length_if_error}; - } - else - { - return false; - } - } - - it_input_current += 3; - } - else if ((byte & 0b1111'1000) == 0b1111'0000) - { - if (it_input_current + 4 > it_input_end) - { - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::TOO_SHORT, .count = length_if_error}; - } - else - { - return false; - } - } - - const auto next_byte_1 = *(it_input_current + 1); - const auto next_byte_2 = *(it_input_current + 2); - const auto next_byte_3 = *(it_input_current + 3); - - if ( - ((next_byte_1 & 0b1100'0000) != 0b1000'0000) or - ((next_byte_2 & 0b1100'0000) != 0b1000'0000) or - ((next_byte_3 & 0b1100'0000) != 0b1000'0000) - ) - { - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::TOO_SHORT, .count = length_if_error}; - } - else - { - return false; - } - } - - // range check - const auto code_point = - (byte & 0b0000'0111) << 18 | - (next_byte_1 & 0b0011'1111) << 12 | - (next_byte_2 & 0b0011'1111) << 6 | - (next_byte_3 & 0b0011'1111); - if (code_point <= 0xffff) - { - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::OVERLONG, .count = length_if_error}; - } - else - { - return false; - } - } - if (0x0010'ffff < code_point) - { - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::TOO_LARGE, .count = length_if_error}; - } - else - { - return false; - } - } - - it_input_current += 4; - } - else - { - // we either have too many continuation bytes or an invalid leading byte - if ((byte & 0b1100'0000) == 0b1000'0000) - { - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::TOO_LONG, .count = length_if_error}; - } - else - { - return false; - } - } - - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::HEADER_BITS, .count = length_if_error}; - } - else - { - return false; - } - } - } - - if constexpr (ReturnResultType) - { - return {.error = ErrorCode::NONE, .count = input_length}; - } - else - { - return true; - } - } - - template - [[nodiscard]] constexpr static auto validate(const pointer_type input) noexcept -> std::conditional_t - { - return validate({input, std::char_traits::length(input)}); - } - - // Finds the previous leading byte starting backward from `current` and validates with errors from there. - // Used to pinpoint the location of an error when an invalid chunk is detected. - // We assume that the stream starts with a leading byte, and to check that it is the case, - // we ask that you pass a pointer to the start of the stream (begin). - [[nodiscard]] constexpr static auto rewind_and_validate( - const pointer_type begin, - const pointer_type current, - const pointer_type end - ) noexcept -> result_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(begin != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(end >= current); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current >= begin); - - // First check that we start with a leading byte - if ((begin[0] & 0b1100'0000) == 0b1000'0000) { return {.error = ErrorCode::TOO_LONG, .count = 0}; } - - size_type extra_count = 0; - // A leading byte cannot be further than 4 bytes away - for (std::ptrdiff_t i = 0; i < 5; ++i) - { - if (const auto byte = static_cast(current[-i]); - (byte & 0b1100'0000) == 0b1000'0000) - { - break; - } - extra_count += 1; - } - - const pointer_type it_current = current - extra_count; - - auto result = validate({it_current, static_cast(end - begin + extra_count)}); - result.count -= extra_count; - return result; - } - - // note: we are not BOM aware - template - [[nodiscard]] constexpr static auto length(const input_type input) noexcept -> size_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - if constexpr (OutputCategory == CharsCategory::ASCII) { return code_points(input); } // NOLINT(bugprone-branch-clone) - else if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) { return input.size(); } - else if constexpr (OutputCategory == CharsCategory::UTF16_LE or OutputCategory == CharsCategory::UTF16_BE or OutputCategory == CharsCategory::UTF16) - { - return std::transform_reduce( - input.begin(), - input.end(), - static_cast(0), - std::plus<>{}, - [](const auto byte) noexcept - { - // -65 is 0b10111111 - return (static_cast(byte) > -65) // - + - (static_cast(byte) >= 240) // - ; - }); - } - else if constexpr (OutputCategory == CharsCategory::UTF32) - { - return code_points(input); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - // note: we are not BOM aware - template - [[nodiscard]] constexpr static auto length(const pointer_type input) noexcept -> size_type - { - return length({input, std::char_traits::length(input)}); - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert( - const input_type input, - typename output_type::pointer output - ) noexcept -> std::conditional_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); - if constexpr (ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(validate(input)); - } - - using output_pointer_type = typename output_type::pointer; - using output_char_type = typename output_type::value_type; - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - pointer_type it_input_current = it_input_begin; - const pointer_type it_input_end = it_input_begin + input_length; - - const output_pointer_type it_output_begin = output; - output_pointer_type it_output_current = it_output_begin; - - if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) - { - std::memcpy(it_output_current, it_input_current, input_length * sizeof(char_type)); - it_input_current += input_length; - it_output_current += input_length; - } - else - { - for (; it_input_current < it_input_end; ++it_input_current) - { - if constexpr (CheckNextBlock) - { - // fixme: step? - constexpr auto step = 16; - - // try to convert the next block of `step` ASCII characters - // if it is safe to read `step` more bytes, check that they are ascii - if (it_input_current + 16 <= it_input_end) - { - static_assert(step % sizeof(std::uint64_t) == 0); - - if (const auto value = [it_input_current]() noexcept - { - if constexpr (step == sizeof(std::uint64_t)) - { - return memory::unaligned_load(it_input_current + 0); - } - else if constexpr (step == sizeof(std::uint64_t) * 2) - { - const auto v1 = memory::unaligned_load(it_input_current + 0); - const auto v2 = memory::unaligned_load(it_input_current + sizeof(uint64_t)); - return v1 | v2; - } - }(); - (value & 0x8080'8080'8080'8080) == 0) - { - std::ranges::transform( - it_input_current, - it_input_current + step, - it_output_current, - [](const auto byte) noexcept { return static_cast(byte); }); - - // (`step` - 1) more step, see `for (; it_input_current < it_input_end; ++it_input_current)` - it_input_current += step - 1; - it_output_current += step; - continue; - } - } - } - - const auto length_if_error = static_cast(it_input_current - it_input_begin); - - // suppose it is not an all ASCII byte sequence - if (const auto leading_byte = static_cast(*it_input_current); - leading_byte < 0b1000'0000) - { - // converting one ASCII byte - if constexpr (OutputCategory == CharsCategory::ASCII) { *it_output_current = static_cast(leading_byte); } // NOLINT(bugprone-branch-clone) - else if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - else if constexpr (OutputCategory == CharsCategory::UTF16_LE or OutputCategory == CharsCategory::UTF16_BE) - { - *it_output_current = [leading_byte]() noexcept -> auto - { - if constexpr ((OutputCategory == CharsCategory::UTF16_LE) != (std::endian::native == std::endian::little)) - { - return std::byteswap(static_cast(leading_byte)); - } - else { return static_cast(leading_byte); } - }(); - } - else if constexpr (OutputCategory == CharsCategory::UTF32) - { - *it_output_current = static_cast(leading_byte); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - - it_output_current += 1; - } - else if ((leading_byte & 0b1110'0000) == 0b1100'0000) - { - // we have a two-byte UTF-8 - // minimal bound checking - if (it_input_current + 1 >= it_input_end) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::TOO_SHORT, .count = length_if_error}; - } - else if constexpr (ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT) { break; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - const auto next_byte = *(it_input_current + 1); - // checks if the next byte is a valid continuation byte in UTF-8. A valid continuation byte starts with 10. - if ((next_byte & 0b1100'0000) != 0b1000'0000) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::TOO_SHORT, .count = length_if_error}; - } - else if constexpr (ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT) { break; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - // assembles the Unicode code point from the two bytes. It does this by discarding the leading 110 and 10 bits from the two bytes, shifting the remaining bits of the first byte, and then combining the results with a bitwise OR operation. - const auto code_point = (leading_byte & 0b0001'1111) << 6 | (next_byte & 0b0011'1111); - - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if (code_point < 0x80) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::OVERLONG, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - if (code_point > - []() noexcept -> auto - { - if constexpr (OutputCategory == CharsCategory::ASCII) { return 0xff; } - else { return 0x7ff; } - }()) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::TOO_LARGE, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - } - - *it_output_current = static_cast( - [code_point]() noexcept - { - if constexpr ( - OutputCategory != CharsCategory::UTF32 and - (OutputCategory == CharsCategory::UTF16_LE) != (std::endian::native == std::endian::little) - ) - { - return std::byteswap(code_point); - } - else { return code_point; } - }()); - - // one more step, see `for (; it_input_current < it_input_end; ++it_input_current)` - it_input_current += 1; - it_output_current += 1; - } - else if ((leading_byte & 0b1111'0000) == 0b1110'0000) - { - // we have a three-byte UTF-8 - if constexpr (OutputCategory == CharsCategory::ASCII) - { - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::TOO_LARGE, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - else if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - else if constexpr ( - OutputCategory == CharsCategory::UTF16_LE or - OutputCategory == CharsCategory::UTF16_BE or - OutputCategory == CharsCategory::UTF32) - { - // minimal bound checking - if (it_input_current + 2 >= it_input_end) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::TOO_SHORT, .count = length_if_error}; - } - else if constexpr (ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT) { break; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - const auto next_byte_1 = *(it_input_current + 1); - const auto next_byte_2 = *(it_input_current + 2); - - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if ( - ((next_byte_1 & 0b1100'0000) != 0b1000'0000) or - ((next_byte_2 & 0b1100'0000) != 0b1000'0000)) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::TOO_SHORT, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - } - - const auto code_point = static_cast( - (leading_byte & 0b0000'1111) << 12 | - (next_byte_1 & 0b0011'1111) << 6 | - (next_byte_2 & 0b0011'1111)); - - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - const auto do_return = [length_if_error](const auto error) noexcept - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = error, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - }; - - if (code_point < 0x800) { return do_return(ErrorCode::OVERLONG); } - if (code_point > 0xffff) { return do_return(ErrorCode::TOO_LARGE); } - if (code_point > 0xd7ff and code_point < 0xe000) { return do_return(ErrorCode::SURROGATE); } - } - - *it_output_current = [cp = static_cast(code_point)]() noexcept - { - if constexpr ( - OutputCategory != CharsCategory::UTF32 and - (OutputCategory == CharsCategory::UTF16_LE) != (std::endian::native == std::endian::little) - ) { return std::byteswap(cp); } - else { return cp; } - }(); - - // 2 more step, see `for (; it_input_current < it_input_end; ++it_input_current)` - it_input_current += 2; - it_output_current += 1; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - else if ((leading_byte & 0b1111'1000) == 0b1111'0000) - { - // we have a four-byte UTF-8 word. - if constexpr (OutputCategory == CharsCategory::ASCII) - { - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::TOO_LARGE, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - else if constexpr (OutputCategory == CharsCategory::UTF8_CHAR or OutputCategory == CharsCategory::UTF8) - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - // NOLINT(bugprone-branch-clone) - else if constexpr ( - OutputCategory == CharsCategory::UTF16_LE or - OutputCategory == CharsCategory::UTF16_BE or - OutputCategory == CharsCategory::UTF32) - { - // minimal bound checking - if (it_input_current + 3 >= it_input_end) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::TOO_SHORT, .count = length_if_error}; - } - else if constexpr (ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT) { break; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - const auto next_byte_1 = *(it_input_current + 1); - const auto next_byte_2 = *(it_input_current + 2); - const auto next_byte_3 = *(it_input_current + 3); - - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - if ( - ((next_byte_1 & 0b1100'0000) != 0b1000'0000) or - ((next_byte_2 & 0b1100'0000) != 0b1000'0000) or - ((next_byte_3 & 0b1100'0000) != 0b1000'0000)) - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::TOO_SHORT, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - } - - const auto code_point = static_cast( - (leading_byte & 0b0000'0111) << 18 | - (next_byte_1 & 0b0011'1111) << 12 | - (next_byte_2 & 0b0011'1111) << 6 | - (next_byte_3 & 0b0011'1111)); - - if constexpr (ProcessPolicy != InputProcessPolicy::ASSUME_VALID_INPUT) - { - const auto do_return = [length_if_error](const auto error) noexcept - { - if constexpr (ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = error, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - }; - - if (code_point <= 0xffff) { return do_return(ErrorCode::TOO_LARGE); } - if (code_point > 0x10'ffff) { return do_return(ErrorCode::TOO_LARGE); } - } - - if constexpr (OutputCategory == CharsCategory::UTF32) - { - *it_output_current = static_cast(code_point); - it_output_current += 1; - } - else - { - const auto [high_surrogate, low_surrogate] = [cp = code_point - 0x1'0000]() noexcept -> auto - { - const auto high = static_cast(0xd800 + (cp >> 10)); - const auto low = static_cast(0xdc00 + (cp & 0x3ff)); - - if constexpr ((OutputCategory == CharsCategory::UTF16_LE) != (std::endian::native == std::endian::little)) - { - return std::make_pair(std::byteswap(high), std::byteswap(low)); - } - else { return std::make_pair(high, low); } - }(); - - *it_output_current = static_cast(high_surrogate); - it_output_current += 1; - - *it_output_current = static_cast(low_surrogate); - it_output_current += 1; - } - - // 3 more step, see `for (; it_input_current < it_input_end; ++it_input_current)` - it_input_current += 3; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - else - { - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { return 0; } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - if ((leading_byte & 0b1100'0000) == 0b1000'0000) - { - // we have too many continuation bytes - return result_type{.error = ErrorCode::TOO_LONG, .count = length_if_error}; - } - // we have an invalid leading byte - return result_type{.error = ErrorCode::HEADER_BITS, .count = length_if_error}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - } - } - - if constexpr ( - ProcessPolicy == InputProcessPolicy::ZERO_IF_ERROR_ELSE_PROCESSED_OUTPUT or - ProcessPolicy == InputProcessPolicy::ASSUME_VALID_INPUT - ) { return static_cast(it_output_current - it_output_begin); } - else if constexpr (ProcessPolicy == InputProcessPolicy::RETURN_RESULT_TYPE) - { - return result_type{.error = ErrorCode::NONE, .count = static_cast(it_input_current - it_input_begin)}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert( - const pointer_type input, - typename output_type::pointer output - ) noexcept -> std::conditional_t - { - return convert({input, std::char_traits::length(input)}, output); - } - - template< - typename StringType, - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - requires requires(StringType& string) - { - string.resize(std::declval()); - { - string.data() - } -> std::convertible_to::pointer>; - } - [[nodiscard]] constexpr static auto convert(const input_type input) noexcept -> StringType - { - StringType result{}; - result.resize(length(input)); - - (void)convert(input, result.data()); - return result; - } - - template< - typename StringType, - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - requires requires(StringType& string) - { - string.resize(std::declval()); - { - string.data() - } -> std::convertible_to::pointer>; - } - [[nodiscard]] constexpr static auto convert(const pointer_type input) noexcept -> StringType - { - StringType result{}; - result.resize(length(input)); - - return convert({input, std::char_traits::length(input)}, result.data()); - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert(const input_type input) noexcept -> std::basic_string::value_type> - { - std::basic_string::value_type> result{}; - result.resize(length(input)); - - (void)convert(input, result.data()); - return result; - } - - template< - CharsCategory OutputCategory, - InputProcessPolicy ProcessPolicy = InputProcessPolicy::RETURN_RESULT_TYPE, - bool CheckNextBlock = true - > - [[nodiscard]] constexpr static auto convert(const pointer_type input) noexcept -> std::basic_string::value_type> - { - std::basic_string::value_type> result{}; - result.resize(length(input)); - - return convert({input, std::char_traits::length(input)}, result.data()); - } - - template - [[nodiscard]] constexpr static auto rewind_and_convert( - const pointer_type furthest_possible_begin, - const input_type input, - typename output_type::pointer output - ) noexcept -> result_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(furthest_possible_begin != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() >= furthest_possible_begin); - // fixme - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(furthest_possible_begin - input.data() <= 3); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(output != nullptr); - - // using output_pointer_type = typename output_type::pointer; - // using output_char_type = typename output_type::value_type; - - const auto input_length = input.size(); - - const pointer_type it_input_begin = input.data(); - // pointer_type it_input_current = it_input_begin; - // const pointer_type it_input_end = it_input_begin + input_length; - - // const output_pointer_type it_output_begin = output; - // output_pointer_type it_output_current = it_output_begin; - - // const auto range = std::ranges::subrange{std::make_reverse_iterator(it_input_current), std::make_reverse_iterator(furthest_possible_begin)}; - const auto range = std::ranges::subrange{furthest_possible_begin, it_input_begin} | std::views::reverse; - // fixme: no leading bytes? - const auto extra_count = std::ranges::distance( - range | - std::views::take_while([](const auto c) noexcept -> bool { return (c & 0b1100'0000) != 0b1000'0000; }) - ); - - const auto it_current = it_input_begin - extra_count; - - auto result = convert({it_current, input_length + extra_count}, output); - if (result.has_error()) { result.count -= extra_count; } - - return result; - } - - [[nodiscard]] constexpr static auto code_points(const input_type input) noexcept -> std::size_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(input.data() != nullptr); - - return std::ranges::count_if( - input, - [](const auto byte) noexcept -> bool - { - const auto b = static_cast(byte); - - // -65 is 0b10111111, anything larger in two-complement's should start a new code point. - return b > -65; - }); - } - }; - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - - template<> - class Scalar<"utf8"> : public scalar_utf8_detail::ScalarUtf8Base {}; - - template<> - class Scalar<"utf8_char"> : public scalar_utf8_detail::ScalarUtf8Base {}; - - template<> - struct scalar_processor_of - { - using type = Scalar<"utf8">; - }; - - template<> - struct scalar_processor_of - { - using type = Scalar<"utf8_char">; - }; - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -} // namespace gal::prometheus::chars diff --git a/src/command_line_parser/command_line_parser.hpp b/src/command_line_parser/command_line_parser.hpp new file mode 100644 index 00000000..9a635c18 --- /dev/null +++ b/src/command_line_parser/command_line_parser.hpp @@ -0,0 +1,17 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#if not defined(CLP_USE_EXPECTED) +#if defined(GAL_PROMETHEUS_COMPILER_DEBUG) +#define CLP_USE_EXPECTED 1 +#else +#define CLP_USE_EXPECTED 0 +#endif +#endif + +#include +#include diff --git a/src/command_line_parser/command_line_parser.ixx b/src/command_line_parser/command_line_parser.ixx deleted file mode 100644 index b9f78a9d..00000000 --- a/src/command_line_parser/command_line_parser.ixx +++ /dev/null @@ -1,912 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -#if GAL_PROMETHEUS_COMPILER_MSVC -#include -#endif - -export module gal.prometheus.command_line_parser; - -import std; -import gal.prometheus.error; -import gal.prometheus.meta; -import gal.prometheus.functional; -import gal.prometheus.string; - -#else -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#endif - -namespace gal::prometheus::infrastructure -{ - using regex_char_type = char; - - template - concept regex_string_type = std::ranges::contiguous_range and std::is_same_v, regex_char_type>; - - using regex_type = std::basic_regex; - template - using regex_match_result_type = std::match_results; - template - using regex_sub_match_type = typename regex_match_result_type::value_type; - template - using regex_token_iterator = std::regex_token_iterator; - - template - auto make_regex(const Range& range) -> regex_type { return regex_type{std::ranges::data(range), std::ranges::size(range)}; } - - template - auto regex_match(const Range& range, regex_match_result_type& result, const regex_type& regex) -> bool - { - return std::regex_match(std::ranges::begin(range), std::ranges::end(range), result, regex); - } - - // ReSharper disable StringLiteralTypo - // ReSharper disable CppTemplateArgumentsCanBeDeduced - #define LIST_SEPARATOR_CHAR ',' - #define LIST_SEPARATOR_CHARS "," - constexpr regex_char_type list_separator_char{LIST_SEPARATOR_CHAR}; - constexpr std::basic_string_view list_separator_chars{LIST_SEPARATOR_CHARS}; - - constexpr std::basic_string_view pattern_boolean_true{"(t|T)(rue)?|1"}; - constexpr std::basic_string_view pattern_boolean_false{"(f|F)(alse)?|0"}; - // result[1] -> sign[-/+/] - // result[2] -> 0b1010101 / 01234567 / 123456789 / 0x123456789abcdef - constexpr std::basic_string_view pattern_integer{"([+-]?)(0b[01]+|0x[0-9a-fA-F]+|0[0-7]*|[1-9][0-9]*)"}; - // --option-name / --option_name / --option.name [= args] - // result[1] -> option-name / option_name / option.name - // result[2] -> =args - // result[3] -> args - // -option-name / -option_name / -option.name - // result[4] -> option-name / option_name / option.name - constexpr std::basic_string_view pattern_option{"--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)"}; - // arg1,arg2, arg3, arg4, arg5, arg6, arg7 - // result[0] -> [arg1,arg2, arg3, arg4, arg5, arg6, arg7] - // result[1] -> [arg1] - // result[2] -> [, arg7] - constexpr std::basic_string_view pattern_list{ - "([[:alnum:]][-_[:alnum:]\\.]*)(" LIST_SEPARATOR_CHARS "\\s*[[:alnum:]][-_[:alnum:]]*)*"}; - constexpr std::basic_string_view pattern_list_separator{LIST_SEPARATOR_CHARS "\\s*"}; - // ReSharper restore CppTemplateArgumentsCanBeDeduced - // ReSharper restore StringLiteralTypo - - #if defined(GAL_PROMETHEUS_COMPILER_DEBUG) - #define GAL_PROMETHEUS_INFRASTRUCTURE_COMMAND_LINE_PARSER_USE_EXPECTED 1 - #endif - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - - class CommandLineOptionNameFormatError final : public error::Exception - { - public: - using Exception::Exception; - - [[noreturn]] static auto panic( - const std::string_view option, - const std::source_location& location = std::source_location::current(), - std::stacktrace stacktrace = std::stacktrace::current() - ) noexcept(false) -> void // - { - error::panic( - std::format("Cannot parse `{}` as option name", option), - location, - std::move(stacktrace) - ); - } - }; - - class CommandLineOptionAlreadyExistsError final : public error::Exception - { - public: - using Exception::Exception; - - [[noreturn]] static auto panic( - const std::string_view option, - const std::source_location& location = std::source_location::current(), - std::stacktrace stacktrace = std::stacktrace::current() - ) noexcept(false) -> void // - { - error::panic( - std::format("Option `{}` already exists!", option), - location, - std::move(stacktrace) - ); - } - }; - - class CommandLineOptionUnrecognizedError final : public error::Exception - { - public: - using Exception::Exception; - - [[noreturn]] static auto panic( - const std::string_view option, - const std::source_location& location = std::source_location::current(), - std::stacktrace stacktrace = std::stacktrace::current() - ) noexcept(false) -> void // - { - error::panic( - std::format("Unrecognized option:\n {}", option), - location, - std::move(stacktrace) - ); - } - }; - - class CommandLineOptionRequiredNotPresentError final : public error::Exception - { - public: - using Exception::Exception; - - [[noreturn]] static auto panic( - const std::string_view option, - const std::source_location& location = std::source_location::current(), - std::stacktrace stacktrace = std::stacktrace::current() - ) noexcept(false) -> void // - { - error::panic( - std::format("Required option `{}` not present", option), - location, - std::move(stacktrace) - ); - } - }; - - class CommandLineOptionRequiredNotSetError final : public error::Exception - { - public: - using Exception::Exception; - - [[noreturn]] static auto panic( - const std::string_view option, - const std::source_location& location = std::source_location::current(), - std::stacktrace stacktrace = std::stacktrace::current() - ) noexcept(false) -> void // - { - error::panic( - std::format("Required option `{}` not set and no default value present", option), - location, - std::move(stacktrace) - ); - } - }; - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END - - using descriptor_boolean = bool; - - struct descriptor_integer - { - enum class Base - { - BINARY = 2, - OCTAL = 8, - DECIMAL = 10, - HEXADECIMAL = 16, - }; - - bool is_negative; - Base base; - // Note that we don't copy the string, and always assume that parsing is done before the string is invalidated! - std::basic_string_view value; - }; - - struct descriptor_option - { - // Note that we don't copy the string, and always assume that parsing is done before the string is invalidated! - std::basic_string_view name; - std::basic_string_view value; - }; - - template - [[nodiscard]] constexpr auto sub_match_to_string_view(const regex_sub_match_type& sub_match) - noexcept -> std::basic_string_view // - { - return std::basic_string_view{sub_match.first, sub_match.second}; - } - - template - using descriptor_list = - std::remove_cvref_t< - decltype( // - std::ranges::subrange{std::declval>(), std::declval>()} | // - std::views::transform(sub_match_to_string_view) | // - std::views::common - ) - >; - - template - [[nodiscard]] auto parse_boolean(const Range& range) -> descriptor_boolean - { - const static auto regex_true = make_regex(pattern_boolean_true); - const static auto regex_false = make_regex(pattern_boolean_false); - - regex_match_result_type result; - if constexpr (True) { regex_match(range, result, regex_true); } - else { regex_match(range, result, regex_false); } - return not result.empty(); - } - - template - [[nodiscard]] auto parse_integer(const Range& range) -> - #if defined(GAL_PROMETHEUS_INFRASTRUCTURE_COMMAND_LINE_PARSER_USE_EXPECTED) - std::expected - #else - std::optional - #endif - { - const static auto regex = make_regex(pattern_integer); - - regex_match_result_type result; - regex_match(range, result, regex); - if (result.size() != 3) - { - #if defined(GAL_PROMETHEUS_INFRASTRUCTURE_COMMAND_LINE_PARSER_USE_EXPECTED) - return std::unexpected{std::format("Cannot parse `{}` as `{}`.", range, meta::name_of())}; - #else - return std::nullopt; - #endif - } - - const auto is_negative = result[1] == '-'; - - const auto& sub = result[2]; - const std::basic_string_view sub_string{sub.first, sub.second}; - if (sub_string.starts_with("0b")) - { - return descriptor_integer{ - .is_negative = is_negative, - .base = descriptor_integer::Base::BINARY, - .value = sub_string.substr(2)}; - } - if (sub_string.starts_with("0x")) - { - return descriptor_integer{ - .is_negative = is_negative, - .base = descriptor_integer::Base::HEXADECIMAL, - .value = sub_string.substr(2)}; - } - if (sub_string.starts_with('0')) - { - return descriptor_integer{ - .is_negative = is_negative, - .base = descriptor_integer::Base::OCTAL, - .value = sub_string.substr(1)}; - } - return descriptor_integer{ - .is_negative = is_negative, - .base = descriptor_integer::Base::DECIMAL, - .value = sub_string}; - } - - template - [[nodiscard]] auto parse_option(const Range& range) -> - #if defined(GAL_PROMETHEUS_INFRASTRUCTURE_COMMAND_LINE_PARSER_USE_EXPECTED) - std::expected - #else - std::optional - #endif - { - const static auto regex = make_regex(pattern_option); - - regex_match_result_type result; - regex_match(range, result, regex); - if (result.size() != 5) - { - #if defined(GAL_PROMETHEUS_INFRASTRUCTURE_COMMAND_LINE_PARSER_USE_EXPECTED) - return std::unexpected{std::format("Cannot parse `{}` as `{}`.", range, meta::name_of())}; - #else - return std::nullopt; - #endif - } - - if (result.length(4) > 0) - { - return descriptor_option{ - .name = {result[4].first, result[4].second}, - .value = ""}; - } - - return descriptor_option{ - .name = {result[1].first, result[1].second}, - .value = {result[3].first, result[3].second}}; - } - - template - [[nodiscard]] auto parse_list(const Range& range) -> - #if defined(GAL_PROMETHEUS_INFRASTRUCTURE_COMMAND_LINE_PARSER_USE_EXPECTED) - std::expected, std::string> - #else - std::optional> - #endif - { - const static auto regex = make_regex(pattern_list); - const static auto regex_separator = make_regex(pattern_list_separator); - - regex_match_result_type result; - regex_match(range, result, regex); - if (result.size() != 3) - { - #if defined(GAL_PROMETHEUS_INFRASTRUCTURE_COMMAND_LINE_PARSER_USE_EXPECTED) - return std::unexpected{std::format("Cannot parse `{}` as `{}`.", range, meta::name_of>())}; - #else - return std::nullopt; - #endif - } - - regex_token_iterator token_iterator{result[0].first, result[0].second, regex_separator, -1}; - - return std::ranges::subrange{token_iterator, regex_token_iterator{}} | - std::views::transform(sub_match_to_string_view) | - std::views::common; - } - - namespace parser - { - // boolean - template - requires std::is_same_v - [[nodiscard]] constexpr auto parse(const Range& range) -> - #if defined(GAL_PROMETHEUS_INFRASTRUCTURE_COMMAND_LINE_PARSER_USE_EXPECTED) - std::expected - #else - std::optional - #endif - { - if (parse_boolean(range)) { return true; } - - if (parse_boolean(range)) { return false; } - - #if defined(GAL_PROMETHEUS_INFRASTRUCTURE_COMMAND_LINE_PARSER_USE_EXPECTED) - return std::unexpected{std::format("Cannot parse `{}` as `{}`.", range, meta::name_of())}; - #else - return std::nullopt; - #endif - } - - // integral - // not bool, avoid ambiguous - template - requires(not std::is_same_v) - [[nodiscard]] constexpr auto parse(const Range& range) -> - #if defined(GAL_PROMETHEUS_INFRASTRUCTURE_COMMAND_LINE_PARSER_USE_EXPECTED) - std::expected - #else - std::optional - #endif - { - #if defined(GAL_PROMETHEUS_INFRASTRUCTURE_COMMAND_LINE_PARSER_USE_EXPECTED) - return parse_integer(range) - .and_then( - [&range](const descriptor_integer& descriptor) -> std::expected - { - const auto& [is_negative, base, value] = descriptor; - const auto base_num = static_cast(base); - - using type = std::make_unsigned_t; - type result; - if ( - const auto [last, error] = std::from_chars(value.data(), value.data() + value.size(), result, base_num); - error != std::errc{} or last != value.data() + value.size()) - { - return std::unexpected{std::format("Cannot parse `{}` as `{}`.", range, meta::name_of())}; - } - - if (is_negative) - { - if constexpr (not std::numeric_limits::is_signed) - { - return std::unexpected{std::format("Cannot parse `{}` as `{}`.", range, meta::name_of())}; - } - - return static_cast(-static_cast(result - 1) - 1); - } - - return result; - }); - #else - if (const auto descriptor = parse_integer(range); - descriptor.has_value()) - { - const auto& [is_negative, base, value] = *descriptor; - const auto base_num = static_cast(base); - - using type = std::make_unsigned_t; - type result; - if ( - const auto [last, error] = std::from_chars(value.data(), value.data() + value.size(), result, base_num); - error != std::errc{} or last != value.data() + value.size()) { return std::nullopt; } - - if (is_negative) - { - if constexpr (not std::numeric_limits::is_signed) { return std::nullopt; } - - return static_cast(-static_cast(result - 1) - 1); - } - - return result; - } - - return std::nullopt; - #endif - } - - // list - // not string, avoid ambiguous - template - requires(not std::is_constructible_v) and - requires - { - parser::parse>( - std::declval()).value() - )>>>() - ); - } - constexpr auto parse(const Range& range, OutRange& out) -> void - { - const auto list = parse_list(range); - if (not list.has_value()) { return; } - - if constexpr (const auto view = *list; - std::is_same_v< - std::remove_cvref_t>( - std::declval>>()))>, // - std::ranges::range_value_t> // - ) - { - std::ranges::for_each( - view, - [&out](const auto& string) -> void // - { - if constexpr (requires { out.emplace_back(parser::parse>(string)); }) // - { - out.emplace_back(parser::parse>(string)); - } - else if constexpr (requires { out.push_back(parser::parse>(string)); }) // - { - out.push_back(parser::parse>(string)); - } - else if constexpr (requires { out.emplace(parser::parse>(string)); }) // - { - out.emplace(parser::parse>(string)); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - }); - } - else - { - OutRange tmp_out{}; - if constexpr (requires { tmp_out.reserve(std::ranges::distance(view)); }) { tmp_out.reserve(std::ranges::distance(view)); } - - for (auto it = std::ranges::begin(view); it != std::ranges::end(view); std::ranges::advance(it, 1)) - { - auto value = parser::parse>(*it); - if (not value.has_value()) { return; } - - if constexpr (requires { tmp_out.emplace_back(*std::move(value)); }) // - { - out.emplace_back(*std::move(value)); - } - else if constexpr (requires { out.push_back(*std::move(value)); }) // - { - out.push_back(*std::move(value)); - } - else if constexpr (requires { out.emplace(*std::move(value)); }) // - { - out.emplace(*std::move(value)); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - if constexpr (requires { out.reserve(tmp_out.size()); }) { out.reserve(tmp_out.size() + out.size()); } - std::ranges::move(tmp_out, std::back_inserter(out)); - } - } - - // list - // not string, avoid ambiguous - template - requires(not std::is_constructible_v) and - requires - { - parser::parse(std::declval(), std::declval()); - } - [[nodiscard]] constexpr auto parse(const Range& range) -> OutRange - { - OutRange result{}; - - parser::parse(range, result); - - return result; - } - - // string / construct from string - // not list, avoid ambiguous - template - requires std::is_constructible_v - [[nodiscard]] constexpr auto parse(const Range& range) noexcept(std::is_nothrow_constructible_v) -> T { return T{range}; } - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - template - class CommandLineOption; - template> - class CommandLineOptionParser; - - template - class CommandLineOption - { - public: - using string_type = StringType; - using string_view_type = StringViewType; - - using parser_type = CommandLineOptionParser; - - friend parser_type; - - private: - struct value_type; - - struct implicit_value_type; - - struct default_value_type - { - string_view_type value; - - [[nodiscard]] constexpr auto operator+(const implicit_value_type& v) const noexcept -> value_type { return {*this, v}; } - - [[nodiscard]] constexpr explicit(false) operator string_view_type() const noexcept { return value; } - }; - - struct implicit_value_type - { - string_view_type value; - - [[nodiscard]] constexpr auto operator+(const default_value_type& v) const noexcept -> value_type { return {v, *this}; } - - [[nodiscard]] constexpr explicit(false) operator string_view_type() const noexcept { return value; } - }; - - struct value_type - { - default_value_type dv; - implicit_value_type iv; - }; - - public: - [[nodiscard]] constexpr static auto default_value(const string_view_type value) noexcept -> default_value_type { return {value}; } - - [[nodiscard]] constexpr static auto implicit_value(const string_view_type value) noexcept -> implicit_value_type { return {value}; } - - private: - // This string has no meaning but can indicate that the current value is set. - constexpr static string_view_type secret_value{"~!@#$%^&*()_+"}; - - string_view_type option_short_format_; - string_view_type option_long_format_; - - value_type value_; - string_view_type current_value_; - - constexpr auto set_value_default() noexcept -> void { current_value_ = value_.dv; } - - constexpr auto set_value_implicit() noexcept -> void { current_value_ = value_.iv; } - - constexpr auto set_value(const string_view_type value = secret_value) noexcept -> void { current_value_ = value; } - - template - requires std::is_same_v, string_view_type> - constexpr auto respawn(AllocateFunction allocator) -> void - { - option_short_format_ = allocator(option_short_format_); - option_long_format_ = allocator(option_long_format_); - - if (not value_.dv.value.empty()) { value_.dv = {allocator(value_.dv.value)}; } - if (not value_.iv.value.empty()) { value_.iv = {allocator(value_.iv.value)}; } - - set_value_implicit(); - } - - public: - constexpr CommandLineOption( - const string_view_type option_short_format, - const string_view_type option_long_format, - const value_type value) noexcept - : option_short_format_{option_short_format}, - option_long_format_{option_long_format}, - value_{value}, - // `Defaults` use implicit_value, if the option is given `explicitly` the `default_value` is used, if the option is given `explicitly` and a value is specified the specified value is used. - current_value_{value_.iv} {} - - [[nodiscard]] constexpr auto set() const noexcept -> bool { return not current_value_.empty(); } - - [[nodiscard]] constexpr auto has_default() const noexcept -> bool { return not value_.dv.empty(); } - - [[nodiscard]] constexpr auto default_value() const noexcept -> string_view_type { return value_.dv; } - - [[nodiscard]] constexpr auto is_default() const noexcept -> bool { return current_value_ == default_value(); } - - [[nodiscard]] constexpr auto has_implicit() const noexcept -> bool { return not value_.iv.empty(); } - - [[nodiscard]] constexpr auto implicit_value() const noexcept -> string_view_type { return value_.iv; } - - [[nodiscard]] constexpr auto is_implicit() const noexcept -> bool { return current_value_ == implicit_value(); } - - template - requires requires - { - parser::parse(std::declval()); - } - [[nodiscard]] auto as() const -> std::optional - { - if (not set()) { return std::nullopt; } - - if constexpr ( - std::is_same_v< - std::remove_cvref_t(std::declval()))>, - T> - ) - { - return parser::parse(current_value_); - } - else - { - auto value = parser::parse(current_value_); - #if defined(GAL_PROMETHEUS_INFRASTRUCTURE_COMMAND_LINE_PARSER_USE_EXPECTED) - if (value.has_value()) { return *std::move(value); } - - std::cerr << std::format("Cannot parse `{}` as `{}`.", current_value_, meta::name_of()); - return std::nullopt; - #else - return value; - #endif - } - } - }; - - template - class CommandLineOptionParser - { - public: - using string_type = StringType; - using string_pool_type = string::StringPool; - using string_view_type = typename string_pool_type::view_type; - - using option_type = CommandLineOption; - using option_list_type = std::unordered_map>; - using option_list_size_type = typename option_list_type::size_type; - - [[nodiscard]] constexpr static auto default_value(const string_view_type value) noexcept -> typename option_type::default_value_type - { - return option_type::default_value(value); - } - - [[nodiscard]] constexpr static auto implicit_value(const string_view_type value) noexcept -> typename option_type::implicit_value_type - { - return option_type::implicit_value(value); - } - - private: - string_pool_type string_pool_; - - option_list_type option_list_; - - bool allow_unrecognized_; - - auto add_option(std::shared_ptr option) -> void - { - option->respawn([this](const string_view_type value) -> string_view_type { return string_pool_.append(value); }); - - auto do_add_option = [this](const string_view_type name, std::shared_ptr o) -> void - { - const auto [_, inserted] = option_list_.emplace(name, std::move(o)); - if (not inserted) { CommandLineOptionAlreadyExistsError::panic(name); } - }; - - if (not option->option_short_format_.empty()) { do_add_option(option->option_short_format_, option); } - do_add_option(option->option_long_format_, option); - } - - auto add_alias(const string_view_type alias_name, const string_view_type target_option_name) -> void - { - const auto target_option_it = option_list_.find(target_option_name); - if (target_option_it == option_list_.end()) { CommandLineOptionRequiredNotPresentError::panic(target_option_name); } - - if (const auto it = option_list_.find(alias_name); - it != option_list_.end()) { CommandLineOptionAlreadyExistsError::panic(alias_name); } - - option_list_.emplace(alias_name, target_option_it->second); - } - - public: - explicit CommandLineOptionParser(const bool allow_unrecognized = false) noexcept - : allow_unrecognized_{allow_unrecognized} {} - - [[nodiscard]] auto options() noexcept -> auto - { - return functional::y_combinator{ - functional::overloaded{ - [this](auto& self, const string_view_type option, const typename option_type::value_type value) -> auto& { - const auto option_names = parse_list(option); - if (not option_names.has_value()) { CommandLineOptionNameFormatError::panic(option); } - - const auto option_view = *option_names; - const auto option_size = std::ranges::distance(option_view); - - if (option_size != 1 and option_size != 2) { CommandLineOptionNameFormatError::panic(option); } - - const auto o1 = string_view_type{*std::ranges::begin(option_view)}; - const auto o2 = option_size == 2 - ? string_view_type{*std::ranges::next(std::ranges::begin(option_view), 1)} - : string_view_type{}; - - const auto short_format = o1.size() < o2.size() ? o1 : o2; - const auto long_format = o1.size() < o2.size() ? o2 : o1; - - auto o = std::make_shared(short_format, long_format, value); - add_option(o); - - return self; - }, - [](auto& self, const string_view_type option, const typename option_type::implicit_value_type value) -> auto& { - std::invoke(self, option, value + typename option_type::default_value_type{}); - return self; - }, - [](auto& self, const string_view_type option, const typename option_type::default_value_type value) -> auto& { - std::invoke(self, option, value + typename option_type::implicit_value_type{}); - return self; - }, - [](auto& self, const string_view_type option) -> auto& { - std::invoke(self, option, typename option_type::value_type{}); - return self; - }, - }}; - } - - [[nodiscard]] auto aliases() noexcept -> auto - { - return functional::y_combinator{ - [this](auto& self, const string_view_type alias_name, const string_view_type target_option_name) -> auto& { - const auto option_names = parse_list(alias_name); - if (not option_names.has_value()) { CommandLineOptionNameFormatError::panic(alias_name); } - - const auto option_view = *option_names; - const auto option_size = std::ranges::distance(option_view); - - if (option_size != 1 and option_size != 2) { CommandLineOptionNameFormatError::panic(alias_name); } - - const auto o1 = string_view_type{*std::ranges::begin(option_view)}; - const auto o2 = option_size == 2 - ? string_view_type{*std::ranges::next(std::ranges::begin(option_view), 1)} - : string_view_type{}; - - add_alias(o1, target_option_name); - if (not o2.empty()) { add_alias(o2, target_option_name); } - - return self; - }}; - } - - template End> - auto parse(const Begin begin, const End end) -> void - { - std::vector unmatched{}; - - std::ranges::for_each( - begin, - end, - [this, &unmatched](const auto& string) -> void - { - const auto option = parse_option(string); - - if (not option.has_value()) - { - unmatched.emplace_back(string); - return; - } - - auto it = option_list_.find(option->name); - if (it == option_list_.end()) - { - unmatched.emplace_back(option->name); - return; - } - - // Since we allow `--option`, we are not sure here whether the string is `--option` or `-option`. - if (option->value.empty()) - { - #if __cpp_lib_ranges_starts_ends_with >= 202106L - if (std::ranges::starts_with(string, string_view_type{"--"})) - #else - if(std::ranges::equal(string_view_type{"--"}, string)) - #endif - { - // --option - it->second->set_value_default(); - } - else - { - // -option - it->second->set_value(); - } - } - else - { - const auto allocated_value = string_pool_.append(option->value); - it->second->set_value(allocated_value); - } - }); - - if (not unmatched.empty()) - { - string_type options{}; - - std::ranges::for_each( - unmatched, - [&options](const string_view_type option) - { - options.push_back('\t'); - options.append(" - "); - options.append(option); - options.push_back('\n'); - }); - - std::cerr << std::format("Unrecognized option:\n {}", options); - - if (not allow_unrecognized_) { CommandLineOptionUnrecognizedError::panic(options); } - } - } - - template - auto parse(const Range& range) -> void - { - parse(std::ranges::begin(range), std::ranges::end(range)); - } - - auto parse() -> void - { - parse(error::command_line_args()); - } - - auto contains(const string_view_type arg_name) const -> bool { return option_list_.contains(arg_name); } - - auto count(const string_view_type arg_name) const -> option_list_size_type { return option_list_.count(arg_name); } - - auto operator[](const string_view_type arg_name) const -> const option_type& - { - const auto it = option_list_.find(arg_name); - if (it == option_list_.end()) { CommandLineOptionRequiredNotPresentError::panic(arg_name); } - - return *it->second; - } - }; - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -} - -#if defined(GAL_PROMETHEUS_INFRASTRUCTURE_COMMAND_LINE_PARSER_USE_EXPECTED) -#undef GAL_PROMETHEUS_INFRASTRUCTURE_COMMAND_LINE_PARSER_USE_EXPECTED -#endif diff --git a/src/command_line_parser/error.hpp b/src/command_line_parser/error.hpp new file mode 100644 index 00000000..7924c336 --- /dev/null +++ b/src/command_line_parser/error.hpp @@ -0,0 +1,110 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include + +#include + +namespace gal::prometheus::clp +{ + class CommandLineOptionNameFormatError final : public platform::Exception + { + public: + using Exception::Exception; + + [[noreturn]] static auto panic( + const std::string_view option, + const std::source_location& location = std::source_location::current(), + std::stacktrace stacktrace = std::stacktrace::current() + ) noexcept(false) -> void // + { + platform::panic( + std::format("Cannot parse `{}` as option name", option), + location, + std::move(stacktrace) + ); + } + }; + + class CommandLineOptionAlreadyExistsError final : public platform::Exception + { + public: + using Exception::Exception; + + [[noreturn]] static auto panic( + const std::string_view option, + const std::source_location& location = std::source_location::current(), + std::stacktrace stacktrace = std::stacktrace::current() + ) noexcept(false) -> void // + { + platform::panic( + std::format("Option `{}` already exists!", option), + location, + std::move(stacktrace) + ); + } + }; + + class CommandLineOptionUnrecognizedError final : public platform::Exception + { + public: + using Exception::Exception; + + [[noreturn]] static auto panic( + const std::string_view option, + const std::source_location& location = std::source_location::current(), + std::stacktrace stacktrace = std::stacktrace::current() + ) noexcept(false) -> void // + { + platform::panic( + std::format("Unrecognized option:\n {}", option), + location, + std::move(stacktrace) + ); + } + }; + + class CommandLineOptionRequiredNotPresentError final : public platform::Exception + { + public: + using Exception::Exception; + + [[noreturn]] static auto panic( + const std::string_view option, + const std::source_location& location = std::source_location::current(), + std::stacktrace stacktrace = std::stacktrace::current() + ) noexcept(false) -> void // + { + platform::panic( + std::format("Required option `{}` not present", option), + location, + std::move(stacktrace) + ); + } + }; + + class CommandLineOptionRequiredNotSetError final : public platform::Exception + { + public: + using Exception::Exception; + + [[noreturn]] static auto panic( + const std::string_view option, + const std::source_location& location = std::source_location::current(), + std::stacktrace stacktrace = std::stacktrace::current() + ) noexcept(false) -> void // + { + platform::panic( + std::format("Required option `{}` not set and no default value present", option), + location, + std::move(stacktrace) + ); + } + }; +} diff --git a/src/command_line_parser/option.hpp b/src/command_line_parser/option.hpp new file mode 100644 index 00000000..6063097e --- /dev/null +++ b/src/command_line_parser/option.hpp @@ -0,0 +1,210 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +namespace gal::prometheus::clp +{ + template> + class CommandLineOptionParser; + + template + class CommandLineOption + { + public: + using string_type = StringType; + using string_view_type = StringViewType; + + using parser_type = CommandLineOptionParser; + friend parser_type; + + private: + struct value_type; + + struct implicit_value_type; + + struct default_value_type + { + string_view_type value; + + [[nodiscard]] constexpr auto operator+(const implicit_value_type& v) const noexcept -> value_type + { + return {*this, v}; + } + + // ReSharper disable once CppNonExplicitConversionOperator + [[nodiscard]] constexpr explicit(false) operator string_view_type() const noexcept + { + return value; + } + }; + + struct implicit_value_type + { + string_view_type value; + + [[nodiscard]] constexpr auto operator+(const default_value_type& v) const noexcept -> value_type + { + return {v, *this}; + } + + // ReSharper disable once CppNonExplicitConversionOperator + [[nodiscard]] constexpr explicit(false) operator string_view_type() const noexcept + { + return value; + } + }; + + struct value_type + { + default_value_type dv; + implicit_value_type iv; + }; + + public: + [[nodiscard]] constexpr static auto default_value(const string_view_type value) noexcept -> default_value_type + { + return {value}; + } + + [[nodiscard]] constexpr static auto implicit_value(const string_view_type value) noexcept -> implicit_value_type + { + return {value}; + } + + private: + // This string has no meaning but can indicate that the current value is set. + constexpr static string_view_type secret_value{"~!@#$%^&*()_+"}; + + string_view_type option_short_format_; + string_view_type option_long_format_; + + value_type value_; + string_view_type current_value_; + + constexpr auto set_value_default() noexcept -> void + { + current_value_ = value_.dv; + } + + constexpr auto set_value_implicit() noexcept -> void + { + current_value_ = value_.iv; + } + + constexpr auto set_value(const string_view_type value = secret_value) noexcept -> void + { + current_value_ = value; + } + + template + requires std::is_same_v, string_view_type> + constexpr auto respawn(AllocateFunction allocator) -> void + { + option_short_format_ = allocator(option_short_format_); + option_long_format_ = allocator(option_long_format_); + + if (not value_.dv.value.empty()) + { + value_.dv = {allocator(value_.dv.value)}; + } + if (not value_.iv.value.empty()) + { + value_.iv = {allocator(value_.iv.value)}; + } + + set_value_implicit(); + } + + public: + constexpr CommandLineOption( + const string_view_type option_short_format, + const string_view_type option_long_format, + const value_type value + ) noexcept + : option_short_format_{option_short_format}, + option_long_format_{option_long_format}, + value_{value}, + // `Defaults` use implicit_value, + // if the option is given `explicitly` the `default_value` is used, + // if the option is given `explicitly` and a value is specified the specified value is used. + current_value_{value_.iv} {} + + [[nodiscard]] constexpr auto set() const noexcept -> bool + { + return not current_value_.empty(); + } + + [[nodiscard]] constexpr auto has_default() const noexcept -> bool + { + return not value_.dv.empty(); + } + + [[nodiscard]] constexpr auto default_value() const noexcept -> string_view_type + { + return value_.dv; + } + + [[nodiscard]] constexpr auto is_default() const noexcept -> bool + { + return current_value_ == default_value(); + } + + [[nodiscard]] constexpr auto has_implicit() const noexcept -> bool + { + return not value_.iv.empty(); + } + + [[nodiscard]] constexpr auto implicit_value() const noexcept -> string_view_type + { + return value_.iv; + } + + [[nodiscard]] constexpr auto is_implicit() const noexcept -> bool + { + return current_value_ == implicit_value(); + } + + template + requires requires + { + regex::parser::parse(std::declval()); + } + [[nodiscard]] constexpr auto as() const -> std::optional + { + if (not set()) + { + return std::nullopt; + } + + if constexpr ( + std::is_same_v< + std::remove_cvref_t(std::declval()))>, + T + > + ) + { + return regex::parser::parse(current_value_); + } + else + { + auto value = regex::parser::parse(current_value_); + #if CLP_USE_EXPECTED + if (value.has_value()) + { + return *std::move(value); + } + + std::println(stderr, "Cannot parse `{}` as `{}`.", current_value_, meta::name_of()); + return std::nullopt; + #else + return value; + #endif + } + } + }; +} diff --git a/src/command_line_parser/parser.hpp b/src/command_line_parser/parser.hpp new file mode 100644 index 00000000..ed26d830 --- /dev/null +++ b/src/command_line_parser/parser.hpp @@ -0,0 +1,299 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include + +// fixme +#if __has_include() +#include +#else +#include +#include +#endif + +#include +#include + +#include +#include + +namespace gal::prometheus::clp +{ + template + class CommandLineOptionParser + { + public: + using string_type = StringType; + using string_pool_type = string::StringPool; + using string_view_type = typename string_pool_type::view_type; + + using option_type = CommandLineOption; + using option_list_type = std::unordered_map>; + using option_list_size_type = typename option_list_type::size_type; + + [[nodiscard]] constexpr static auto default_value(const string_view_type value) noexcept -> typename option_type::default_value_type + { + return option_type::default_value(value); + } + + [[nodiscard]] constexpr static auto implicit_value(const string_view_type value) noexcept -> typename option_type::implicit_value_type + { + return option_type::implicit_value(value); + } + + private: + string_pool_type string_pool_; + + option_list_type option_list_; + + bool allow_unrecognized_; + + constexpr auto do_add_option(const std::shared_ptr& option) -> void + { + option->respawn([this](const string_view_type value) -> string_view_type + { + return string_pool_.add(value); + }); + + auto add = [this](const string_view_type name, std::shared_ptr o) -> void + { + const auto [_, inserted] = option_list_.emplace(name, std::move(o)); + if (not inserted) + { + CommandLineOptionAlreadyExistsError::panic(name); + } + }; + + if (not option->option_short_format_.empty()) + { + add(option->option_short_format_, option); + } + add(option->option_long_format_, option); + } + + constexpr auto do_add_alias(const string_view_type alias_name, const string_view_type target_option_name) -> void + { + const auto target_option_it = option_list_.find(target_option_name); + if (target_option_it == option_list_.end()) + { + CommandLineOptionRequiredNotPresentError::panic(target_option_name); + } + + if (const auto it = option_list_.find(alias_name); + it != option_list_.end()) + { + CommandLineOptionAlreadyExistsError::panic(alias_name); + } + + option_list_.emplace(alias_name, target_option_it->second); + } + + public: + constexpr explicit CommandLineOptionParser(const bool allow_unrecognized = false) noexcept + : allow_unrecognized_{allow_unrecognized} {} + + constexpr auto add_option(const string_view_type option, const typename option_type::value_type value) -> CommandLineOptionParser& + { + const auto option_names = regex::parse_list(option); + if (not option_names.has_value()) + { + CommandLineOptionNameFormatError::panic(option); + } + + const auto& option_view = *option_names; + const auto option_size = std::ranges::distance(option_view); + + if (option_size != 1 and option_size != 2) + { + CommandLineOptionNameFormatError::panic(option); + } + + const auto o1 = string_view_type{*std::ranges::begin(option_view)}; + const auto o2 = option_size == 2 + ? string_view_type{*std::ranges::next(std::ranges::begin(option_view), 1)} + : string_view_type{}; + + const auto short_format = o1.size() < o2.size() ? o1 : o2; + const auto long_format = o1.size() < o2.size() ? o2 : o1; + + auto o = std::make_shared(short_format, long_format, value); + this->do_add_option(o); + + return *this; + } + + constexpr auto add_option(const string_view_type option, const typename option_type::implicit_value_type value) -> CommandLineOptionParser& + { + return this->add_option(option, value + typename option_type::default_value_type{}); + } + + constexpr auto add_option(const string_view_type option, const typename option_type::default_value_type value) -> CommandLineOptionParser& + { + return this->add_option(option, value + typename option_type::implicit_value_type{}); + } + + constexpr auto add_option(const string_view_type option) -> CommandLineOptionParser& + { + return this->add_option(option, typename option_type::value_type{}); + } + + constexpr auto add_alias(const string_view_type alias_name, const string_view_type target_option_name) -> CommandLineOptionParser& + { + const auto option_names = regex::parse_list(alias_name); + if (not option_names.has_value()) + { + CommandLineOptionNameFormatError::panic(alias_name); + } + + const auto& option_view = *option_names; + const auto option_size = std::ranges::distance(option_view); + + if (option_size != 1 and option_size != 2) + { + CommandLineOptionNameFormatError::panic(alias_name); + } + + const auto o1 = string_view_type{*std::ranges::begin(option_view)}; + const auto o2 = option_size == 2 + ? string_view_type{*std::ranges::next(std::ranges::begin(option_view), 1)} + : string_view_type{}; + + this->do_add_alias(o1, target_option_name); + if (not o2.empty()) + { + this->do_add_alias(o2, target_option_name); + } + + return *this; + } + + template End> + constexpr auto parse(const Begin begin, const End end) -> void + { + std::vector unmatched{}; + + std::ranges::for_each( + begin, + end, + [this, &unmatched](const auto& string) -> void + { + const auto option = regex::parse_option(string); + + if (not option.has_value()) + { + unmatched.emplace_back(string); + return; + } + + auto it = option_list_.find(option->name); + if (it == option_list_.end()) + { + unmatched.emplace_back(option->name); + return; + } + + // Since we allow `--option`, we are not sure here whether the string is `--option` or `-option`. + if (option->value.empty()) + { + #if __cpp_lib_ranges_starts_ends_with >= 202106L + if (std::ranges::starts_with(string, string_view_type{"--"})) + #else + if (std::ranges::equal(string_view_type{"--"}, string)) + #endif + { + // --option + it->second->set_value_default(); + } + else + { + // -option + it->second->set_value(); + } + } + else + { + const auto allocated_value = string_pool_.add(option->value); + it->second->set_value(allocated_value); + } + } + ); + + if (not unmatched.empty()) + { + string_type options{}; + + std::ranges::for_each( + unmatched, + [&options](const string_view_type option) + { + options.push_back('\t'); + options.append(" - "); + options.append(option); + options.push_back('\n'); + } + ); + + if (not allow_unrecognized_) + { + CommandLineOptionUnrecognizedError::panic(options); + } + + #if __has_include() + std::println(stderr, "Unrecognized option:\n {}", options); + #else + const auto output = std::format("Unrecognized option:\n {}", options); + std::fputs(output.c_str(), stderr); + std::putc('\n', stderr); + #endif + } + } + + template + constexpr auto parse(const Range& range) -> void + { + this->parse(std::ranges::begin(range), std::ranges::end(range)); + } + + auto parse() -> void + { + const auto args = platform::command_args(); + this->parse( + args | + std::views::drop(1) | // skip argv[0] + std::views::transform( + [](const auto& p) noexcept -> std::string_view + { + return {p}; + } + ) + ); + } + + [[nodiscard]] constexpr auto contains(const string_view_type arg_name) const -> bool + { + return option_list_.contains(arg_name); + } + + [[nodiscard]] constexpr auto count(const string_view_type arg_name) const -> option_list_size_type + { + return option_list_.count(arg_name); + } + + [[nodiscard]] constexpr auto operator[](const string_view_type arg_name) const -> const option_type& + { + const auto it = option_list_.find(arg_name); + if (it == option_list_.end()) + { + CommandLineOptionRequiredNotPresentError::panic(arg_name); + } + + return *it->second; + } + }; +} diff --git a/src/command_line_parser/regex.hpp b/src/command_line_parser/regex.hpp new file mode 100644 index 00000000..aa3f617c --- /dev/null +++ b/src/command_line_parser/regex.hpp @@ -0,0 +1,600 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#if CLP_USE_EXPECTED +#include +#else +#include +#endif + +#include + +#if CLP_USE_EXPECTED +#include +#endif + +namespace gal::prometheus::clp::regex +{ + #if not defined(CLP_IDENTIFIER) + #define CLP_IDENTIFIER "[[:alnum:]][-_[:alnum:]\\.]*" + #endif + + #if not defined(CLP_LIST_SEPARATOR) + #define CLP_LIST_SEPARATOR "," + #endif + + #define CLP_LIST_SEPARATOR_IGNORE_WS CLP_LIST_SEPARATOR "\\s*" + + using char_type = char; + + template + concept string_type = std::ranges::contiguous_range and std::is_same_v, char_type>; + + using regex_type = std::basic_regex; + template + using regex_match_result_type = std::match_results; + template + using regex_sub_match_type = typename regex_match_result_type::value_type; + template + using regex_token_iterator = std::regex_token_iterator; + + template + [[nodiscard]] constexpr auto make( + const Range& range, + const regex_type::flag_type flag = regex_type::ECMAScript | regex_type::optimize + ) -> regex_type + { + return regex_type{std::ranges::data(range), std::ranges::size(range), flag}; + } + + template + [[nodiscard]] constexpr auto match( + const Range& range, + regex_match_result_type& result, + const regex_type& regex + ) -> bool + { + return std::regex_match(std::ranges::begin(range), std::ranges::end(range), result, regex); + } + + // ReSharper disable StringLiteralTypo + // ReSharper disable CommentTypo + // ReSharper disable CppTemplateArgumentsCanBeDeduced + // ReSharper disable GrammarMistakeInComment + + constexpr std::basic_string_view pattern_integer + { + // // result[0] -> [-/+] 0b1010101 / 0x123456789abcdef / 01234567 / 123456789 + // // result[1] -> "-" / "+" / "" + // "([-+]?)" + // "(?:" + // // B + // // result[2] -> "0b" / "" + // // result[3] -> "1010101" / "" + // "(0b)([01]+)" + // "|" // or + // // H + // // result[4] -> "0x" / "" + // // result[5] -> "123456789abcdef" / "" + // "(0x)([0-9a-fA-F]+)" + // "|" + // // O + // // result[6] -> "0" / "" + // // result[7] -> "1234567" / "" + // "(0)([0-7]+)" + // "|" // or + // // D + // // result[8] -> "123456789" / "" + // "([1-9][0-9]*" + // "|" // or + // // D + // // result[8] -> "0" / "" + // "0)" + // ")" + + // result[0] -> [-/+] 0b1010101 / 0x123456789abcdef / 01234567 / 123456789 + // result[1] -> "-" / "+" / "" + "([-+]?)" + // result[2] -> "0b1010101" / "0x123456789abcdef" / "01234567" / "123456789" + "(" + // B + "0b[01]+" + "|" // or + // H + "0x[0-9a-fA-F]+" + "|" // or + // O + "0[0-7]*" + "|" // or + // D + "[1-9][0-9]*" + ")" + }; + + constexpr std::basic_string_view pattern_option + { + // --option-name / --option_name / --option.name + "--(" CLP_IDENTIFIER ")" + // [= args] + "(?:=(.*))?" + // result[0] -> --option-name / --option_name / --option.name [= args] + // result[1] -> option-name / option_name / option.name + // result[2] -> args + // result[3] -> "" + "|" // or + // -option-name / -option_name / -option.name + "^-(?!-)(" CLP_IDENTIFIER ")$" + // result[0] -> -option-name / -option_name / -option.name + // result[1] -> "" + // result[2] -> "" + // result[3] -> option-name / option_name / option.name + }; + + // arg1,arg2, arg3, arg4, arg5, arg6, arg7 + // result[0] -> [arg1,arg2, arg3, arg4, arg5, arg6, arg7] + constexpr std::basic_string_view pattern_list + { + // arg1 + CLP_IDENTIFIER + // [,arg2, arg3, arg4, arg5, arg6, arg7] + "(?:" CLP_LIST_SEPARATOR_IGNORE_WS CLP_IDENTIFIER ")*" + }; + // arg1,arg2, arg3, arg4, arg5, arg6, arg7 + // [arg1, arg2, arg3, arg4, arg5, arg6, arg7] + constexpr std::basic_string_view pattern_list_separator + { + CLP_LIST_SEPARATOR_IGNORE_WS + }; + + // ReSharper restore GrammarMistakeInComment + // ReSharper restore CppTemplateArgumentsCanBeDeduced + // ReSharper restore CommentTypo + // ReSharper restore StringLiteralTypo + + using descriptor_boolean = bool; + + struct descriptor_integer + { + enum class Base : std::uint8_t + { + BINARY = 2, + OCTAL = 8, + DECIMAL = 10, + HEXADECIMAL = 16, + }; + + bool is_negative; + Base base; + // Note that we don't copy the string, and always assume that parsing is done before the string is invalidated! + std::basic_string_view value; + }; + + struct descriptor_option + { + // Note that we don't copy the string, and always assume that parsing is done before the string is invalidated! + std::basic_string_view name; + std::basic_string_view value; + }; + + template + [[nodiscard]] constexpr auto sub_match_to_string_view( + const regex_sub_match_type& sub_match + ) noexcept -> std::basic_string_view + { + return std::basic_string_view{sub_match.first, sub_match.second}; + } + + template + using descriptor_list = + std::remove_cvref_t< + decltype( // + std::ranges::subrange{std::declval>(), std::declval>()} | // + std::views::transform(sub_match_to_string_view) | // + std::views::common + ) + >; + + template + [[nodiscard]] auto parse_boolean(const Range& range) -> descriptor_boolean + { + if constexpr (True) + { + constexpr static std::basic_string_view candidates[] + { + "Y", "y", + "YES", "Yes", "yes", + "ON", "On", "on", + "TRUE", "True", "true", + "1" + }; + + return std::ranges::contains(candidates, range); + } + else + { + constexpr static std::basic_string_view candidates[] + { + "N", "n", + "NO", "No", "no", + "OFF", "Off", "off", + "FALSE", "False", "false", + "0" + }; + + if (std::ranges::empty(range)) + { + return true; + } + return std::ranges::contains(candidates, range); + } + } + + template + [[nodiscard]] auto parse_integer(const Range& range) -> + #if CLP_USE_EXPECTED + std::expected + #else + std::optional + #endif + { + const static auto regex = regex::make(pattern_integer); + + regex_match_result_type result; + const auto match_result = regex::match(range, result, regex); + + if (not match_result or result.size() != 3) + { + #if CLP_USE_EXPECTED + return std::unexpected{std::format("Cannot parse `{}` as `{}`.", range, meta::name_of())}; + #else + return std::nullopt; + #endif + } + + const auto is_negative = result[1] == '-'; + + const auto& sub = result[2]; + const auto sub_string = regex::sub_match_to_string_view(sub); + + if (sub_string.starts_with("0b")) + { + return descriptor_integer + { + .is_negative = is_negative, + .base = descriptor_integer::Base::BINARY, + .value = sub_string.substr(2) + }; + } + + if (sub_string.starts_with("0x")) + { + return descriptor_integer + { + .is_negative = is_negative, + .base = descriptor_integer::Base::HEXADECIMAL, + .value = sub_string.substr(2) + }; + } + + if (sub_string.starts_with('0') and sub_string.size() > 1) + { + return descriptor_integer + { + .is_negative = is_negative, + .base = descriptor_integer::Base::OCTAL, + .value = sub_string.substr(1) + }; + } + + return descriptor_integer + { + .is_negative = is_negative, + .base = descriptor_integer::Base::DECIMAL, + .value = sub_string + }; + } + + template + [[nodiscard]] auto parse_option(const Range& range) -> + #if CLP_USE_EXPECTED + std::expected + #else + std::optional + #endif + { + const static auto regex = regex::make(pattern_option); + + regex_match_result_type result; + const auto match_result = regex::match(range, result, regex); + + if (not match_result or (not result[1].matched and not result[3].matched)) + { + #if CLP_USE_EXPECTED + return std::unexpected{std::format("Cannot parse `{}` as `{}`.", range, meta::name_of())}; + #else + return std::nullopt; + #endif + } + + if (result[1].matched) + { + return descriptor_option + { + .name = regex::sub_match_to_string_view(result[1]), + .value = regex::sub_match_to_string_view(result[2]) + }; + } + + return descriptor_option + { + .name = regex::sub_match_to_string_view(result[3]), + .value = "" + }; + } + + template + [[nodiscard]] auto parse_list(const Range& range) -> + #if CLP_USE_EXPECTED + std::expected, std::string> + #else + std::optional> + #endif + { + const static auto regex = regex::make(pattern_list); + const static auto regex_separator = regex::make(pattern_list_separator); + + regex_match_result_type result; + const auto match_result = regex::match(range, result, regex); + + if (not match_result or result.size() != 1) + { + #if CLP_USE_EXPECTED + return std::unexpected{std::format("Cannot parse `{}` as `{}`.", range, meta::name_of>())}; + #else + return std::nullopt; + #endif + } + + regex_token_iterator token_iterator{result[0].first, result[0].second, regex_separator, -1}; + + return std::ranges::subrange{token_iterator, regex_token_iterator{}} | + std::views::transform(sub_match_to_string_view) | + std::views::common; + } + + struct parser + { + // boolean + template, string_type Range> + [[nodiscard]] constexpr static auto parse(const Range& range) -> + #if CLP_USE_EXPECTED + std::expected + #else + std::optional + #endif + { + if (regex::parse_boolean(range)) { return true; } + + if (regex::parse_boolean(range)) { return false; } + + #if CLP_USE_EXPECTED + return std::unexpected{std::format("Cannot parse `{}` as `{}`.", range, meta::name_of())}; + #else + return std::nullopt; + #endif + } + + // integral + // not bool, avoid ambiguous + template + requires(not std::is_same_v) + [[nodiscard]] constexpr static auto parse(const Range& range) -> + #if CLP_USE_EXPECTED + std::expected + #else + std::optional + #endif + { + #if CLP_USE_EXPECTED + return regex::parse_integer(range) + .and_then( + [&range](const descriptor_integer& descriptor) -> std::expected + { + const auto& [is_negative, base, value] = descriptor; + const auto base_num = static_cast(base); + + using type = std::make_unsigned_t; + + type result; + if (const auto [last, error] = std::from_chars(value.data(), value.data() + value.size(), result, base_num); + error != std::errc{} or last != value.data() + value.size()) + { + return std::unexpected{std::format("Cannot parse `{}` as `{}`.", range, meta::name_of())}; + } + + if (is_negative) + { + if constexpr (std::numeric_limits::is_signed) + { + return static_cast(-static_cast(result - 1) - 1); + } + else + { + return std::unexpected{std::format("Cannot parse `{}` as `{}`.", range, meta::name_of())}; + } + } + + return result; + } + ); + #else + if (const auto descriptor = regex::parse_integer(range); + descriptor.has_value()) + { + const auto& [is_negative, base, value] = *descriptor; + const auto base_num = static_cast(base); + + using type = std::make_unsigned_t; + + type result; + if (const auto [last, error] = std::from_chars(value.data(), value.data() + value.size(), result, base_num); + error != std::errc{} or last != value.data() + value.size()) + { + return std::nullopt; + } + + if (is_negative) + { + if constexpr (not std::numeric_limits::is_signed) + { + return std::nullopt; + } + else + { + return static_cast(-static_cast(result - 1) - 1); + } + } + + return result; + } + + return std::nullopt; + #endif + } + + /** + * @note MSVC is more permissive in how it handles two-phase lookup and constraint satisfaction, + * it effectively defers or relaxes some aspects of template instantiation until it sees the exact call. + * In contrast, Clang (and GCC) more strictly follows the standard rules for two-phase lookup and concepts. + * Consequently, when Clang sees the requires-expression involving something like(parser::parse): + * @code + * parser::parse>( + * std::declval>>() + * ); + * @endcode + * it attempts to instantiate and check this call immediately during constraint checks. + * If no matching function was found (see no valid definition for that template function yet), Clang flags it as an error. + * MSVC, on the other hand, either does not perform this check as strictly or delays it, + * thus allowing the code to compile successfully in scenarios where Clang (and GCC) would reject it. + */ + + // string / construct from string + // not list, avoid ambiguous + template + requires std::is_constructible_v + [[nodiscard]] constexpr static auto parse(const Range& range) noexcept(std::is_nothrow_constructible_v) -> T + { + return T{range}; + } + + // list + // not string, avoid ambiguous + template + requires(not std::is_constructible_v) + constexpr static auto parse(const Range& range, OutRange& out) -> void // + requires requires + { + parser::parse>( + std::declval descriptor_list::const_reference + regex::parse_list(range).value() // range -> descriptor_list + )>>>() + ); + } + { + const auto list = regex::parse_list(range); + if (not list.has_value()) + { + return; + } + + if constexpr (const auto& view = list.value(); + std::is_same_v< + std::remove_cvref_t>( + std::declval>>() + ) + )>, // + std::ranges::range_value_t + > // + ) + { + std::ranges::for_each( + view, + [&out](const auto& string) -> void // + { + if constexpr (requires { out.emplace_back(parser::parse>(string)); }) // + { + out.emplace_back(parser::parse>(string)); + } + else if constexpr (requires { out.push_back(parser::parse>(string)); }) // + { + out.push_back(parser::parse>(string)); + } + else if constexpr (requires { out.emplace(parser::parse>(string)); }) // + { + out.emplace(parser::parse>(string)); + } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + }); + } + else + { + OutRange tmp_out{}; + if constexpr (requires { tmp_out.reserve(std::ranges::distance(view)); }) { tmp_out.reserve(std::ranges::distance(view)); } + + for (auto it = std::ranges::begin(view); it != std::ranges::end(view); std::ranges::advance(it, 1)) + { + auto value = parser::parse>(*it); + if (not value.has_value()) { return; } + + if constexpr (requires { tmp_out.emplace_back(*std::move(value)); }) // + { + out.emplace_back(*std::move(value)); + } + else if constexpr (requires { out.push_back(*std::move(value)); }) // + { + out.push_back(*std::move(value)); + } + else if constexpr (requires { out.emplace(*std::move(value)); }) // + { + out.emplace(*std::move(value)); + } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + if constexpr (requires { out.reserve(tmp_out.size()); }) { out.reserve(tmp_out.size() + out.size()); } + std::ranges::move(tmp_out, std::back_inserter(out)); + } + } + + // list + // not string, avoid ambiguous + template + requires(not std::is_constructible_v) and + requires + { + parser::parse(std::declval(), std::declval()); + } + [[nodiscard]] constexpr static auto parse(const Range& range) -> OutRange + { + OutRange result{}; + + parser::parse(range, result); + + return result; + } + }; +} diff --git a/src/concurrency/concurrency.hpp b/src/concurrency/concurrency.hpp new file mode 100644 index 00000000..2334c445 --- /dev/null +++ b/src/concurrency/concurrency.hpp @@ -0,0 +1,9 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include diff --git a/src/concurrency/concurrency.ixx b/src/concurrency/concurrency.ixx deleted file mode 100644 index 3e6a640a..00000000 --- a/src/concurrency/concurrency.ixx +++ /dev/null @@ -1,18 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -export module gal.prometheus.concurrency; - -export import :thread; -export import :unfair_mutex; - -#else -#pragma once - -#include -#include - -#endif diff --git a/src/concurrency/queue.hpp b/src/concurrency/queue.hpp new file mode 100644 index 00000000..ee9f3344 --- /dev/null +++ b/src/concurrency/queue.hpp @@ -0,0 +1,892 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +#if defined(__x86_64__) or defined(_M_X64) or defined(__i386__) or defined(_M_IX86) +#define GAL_PROMETHEUS_CONCURRENCY_QUEUE_X86 +#include +#elif defined(__arm__) or defined(__aarch64__) or defined(_M_ARM64) +#define GAL_PROMETHEUS_CONCURRENCY_QUEUE_ARM +#endif + +namespace gal::prometheus::concurrency +{ + namespace queue_detail + { + inline auto spin_loop_pause() noexcept -> void + { + #if defined(GAL_PROMETHEUS_CONCURRENCY_QUEUE_X86) + _mm_pause(); + #elif defined(GAL_PROMETHEUS_CONCURRENCY_QUEUE_ARM) + __yield(); + #else + #error "fixme" + #endif + } + + constexpr auto cache_line_size = std::hardware_constructive_interference_size; + + template + struct cache_line_index_bits; + + template<> + struct cache_line_index_bits<(1 << 1)> : std::integral_constant {}; + + template<> + struct cache_line_index_bits<(1 << 2)> : std::integral_constant {}; + + template<> + struct cache_line_index_bits<(1 << 3)> : std::integral_constant {}; + + template<> + struct cache_line_index_bits<(1 << 4)> : std::integral_constant {}; + + template<> + struct cache_line_index_bits<(1 << 5)> : std::integral_constant {}; + + template<> + struct cache_line_index_bits<(1 << 6)> : std::integral_constant {}; + + template<> + struct cache_line_index_bits<(1 << 7)> : std::integral_constant {}; + + template<> + struct cache_line_index_bits<(1 << 8)> : std::integral_constant {}; + + template + struct index_shuffle_bits + { + constexpr static auto index_bits = cache_line_index_bits::value; + constexpr static auto min_size = static_cast(1) << (index_bits * 2); + + constexpr static auto value = ElementCount < min_size ? 0 : index_bits; + }; + + template + [[nodiscard]] constexpr auto mapping_index(const std::size_t index) noexcept -> std::size_t + { + if constexpr (ShuffleBits == 0) + { + return index; + } + else + { + constexpr auto mix_mask = (static_cast(1) << ShuffleBits) - 1; + + const auto mix = (index ^ (index >> ShuffleBits)) & mix_mask; + return index ^ mix ^ (mix << ShuffleBits); + } + } + + template + [[nodiscard]] constexpr auto mapping(std::span elements, const std::size_t index) noexcept -> T& + { + return elements[mapping_index(index)]; + } + + template + [[nodiscard]] constexpr auto mapping(T* elements, const std::size_t index) noexcept -> T& + { + return elements[mapping_index(index)]; + } + + template + class QueueImpl + { + public: + using derived_type = Derived; + + using size_type = unsigned; + using atomic_size_type = std::atomic; + + enum class State : std::uint8_t + { + EMPTY, + LOADING, + STORING, + STORED, + }; + + using atomic_state_type = std::atomic; + + protected: + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH + + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(4324) + #else + #endif + + alignas(cache_line_size) atomic_size_type head_; + alignas(cache_line_size) atomic_size_type tail_; + + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP + + private: + [[nodiscard]] constexpr auto rep() noexcept -> derived_type& + { + return *static_cast(this); + } + + [[nodiscard]] constexpr auto rep() const noexcept -> const derived_type& + { + return *static_cast(this); + } + + public: + QueueImpl() noexcept = default; + + QueueImpl(const QueueImpl& other) noexcept + : head_{other.head_.load(std::memory_order::relaxed)}, + tail_{other.tail_.load(std::memory_order::relaxed)} {} + + auto operator=(const QueueImpl& other) noexcept -> QueueImpl& + { + head_.store(other.head_.load(std::memory_order::relaxed), std::memory_order::relaxed); + tail_.store(other.tail_.load(std::memory_order::relaxed), std::memory_order::relaxed); + + return *this; + } + + QueueImpl(QueueImpl&&) noexcept = default; + + auto operator=(QueueImpl&&) noexcept -> QueueImpl& = default; + + protected: + ~QueueImpl() noexcept = default; + + public: + auto swap(QueueImpl& other) noexcept -> void + { + other.head_.store(head_.exchange(other.head_.load(std::memory_order::relaxed)), std::memory_order::relaxed); + other.tail_.store(tail_.exchange(other.tail_.load(std::memory_order::relaxed)), std::memory_order::relaxed); + } + + friend auto swap(QueueImpl& lhs, QueueImpl& rhs) noexcept -> void + { + lhs.swap(rhs); + } + + protected: + template + constexpr static auto do_pop_atomic(std::atomic& atomic_element) noexcept -> T + { + if constexpr (derived_type::is_single_producer_single_consumer) + { + while (true) + { + if (auto element = atomic_element.load(std::memory_order::acquire); + element != NilValue) + [[likely]] + { + atomic_element.store(NilValue, std::memory_order::relaxed); + return element; + } + + if constexpr (derived_type::maximize_throughput) + { + spin_loop_pause(); + } + } + } + else + { + while (true) + { + if (auto element = atomic_element.exchange(NilValue, std::memory_order::acquire); + element != NilValue) + [[likely]] + { + return element; + } + + spin_loop_pause(); + if constexpr (derived_type::maximize_throughput) + { + while (atomic_element.load(std::memory_order::relaxed) == NilValue) + { + spin_loop_pause(); + } + } + } + } + } + + template + constexpr static auto do_push_atomic(std::atomic& atomic_element, T element) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(element != NilValue); + + if constexpr (derived_type::is_single_producer_single_consumer) + { + while (true) + { + if (atomic_element.load(std::memory_order::relaxed) == NilValue) + [[likely]] + { + atomic_element.store(element, std::memory_order::release); + return; + } + + if constexpr (derived_type::maximize_throughput) + { + spin_loop_pause(); + } + } + } + else + { + while (true) + { + if (auto expected = NilValue; + atomic_element.compare_exchange_weak(expected, element, std::memory_order::release, std::memory_order::relaxed)) + [[likely]] + { + return; + } + + spin_loop_pause(); + if constexpr (derived_type::maximize_throughput) + { + while (atomic_element.load(std::memory_order::relaxed) != NilValue) + { + spin_loop_pause(); + } + } + } + } + } + + template + constexpr static auto do_pop_atomic(atomic_state_type& atomic_state, T& in_element) noexcept -> T + { + if constexpr (derived_type::is_single_producer_single_consumer) + { + while (true) + { + if (atomic_state.load(std::memory_order::acquire) == State::STORED) + [[likely]] + { + auto out_element = std::move(in_element); + atomic_state.store(State::EMPTY, std::memory_order::release); + return out_element; + } + + if constexpr (derived_type::maximize_throughput) + { + spin_loop_pause(); + } + } + } + else + { + while (true) + { + if (auto expected = State::STORED; + atomic_state.compare_exchange_weak(expected, State::LOADING, std::memory_order::acquire, std::memory_order::relaxed)) + [[likely]] + { + auto out_element = std::move(in_element); + atomic_state.store(State::EMPTY, std::memory_order::release); + return out_element; + } + + spin_loop_pause(); + if constexpr (derived_type::maximize_throughput) + { + while (atomic_state.load(std::memory_order::relaxed) != State::STORED) + { + spin_loop_pause(); + } + } + } + } + } + + template + constexpr static auto do_push_atomic(atomic_state_type& atomic_state, T& out_element, T&& in_element) noexcept -> void + { + if constexpr (derived_type::is_single_producer_single_consumer) + { + while (true) + { + if (atomic_state.load(std::memory_order::acquire) == State::EMPTY) + [[likely]] + { + out_element = std::forward(in_element); + atomic_state.store(State::STORED, std::memory_order::release); + return; + } + + if constexpr (derived_type::maximize_throughput) + { + spin_loop_pause(); + } + } + } + else + { + while (true) + { + if (auto expected = State::EMPTY; + atomic_state.compare_exchange_weak(expected, State::STORING, std::memory_order::acquire, std::memory_order::relaxed)) + [[likely]] + { + out_element = std::forward(in_element); + atomic_state.store(State::STORED, std::memory_order::release); + return; + } + + spin_loop_pause(); + if constexpr (derived_type::maximize_throughput) + { + while (atomic_state.load(std::memory_order::relaxed) != State::EMPTY) + { + spin_loop_pause(); + } + } + } + } + } + + public: + [[nodiscard]] constexpr auto try_pop() noexcept -> auto + { + auto tail = tail_.load(std::memory_order::relaxed); + if constexpr (derived_type::is_single_producer_single_consumer) + { + if (std::cmp_less_equal(head_.load(std::memory_order::relaxed), tail)) + { + return std::nullopt; + } + tail_.store(tail + 1, std::memory_order::relaxed); + } + else + { + while (true) + { + if (std::cmp_less_equal(head_.load(std::memory_order::relaxed), tail)) + { + return std::nullopt; + } + + if (tail_.compare_exchange_weak(tail, tail + 1, std::memory_order::relaxed, std::memory_order::relaxed)) + [[likely]] + { + break; + } + } + } + + return std::optional{std::in_place, rep().do_pop(tail)}; + } + + template + [[nodiscard]] constexpr auto try_push(Args&&... args) noexcept -> bool + { + auto head = head_.load(std::memory_order::relaxed); + if constexpr (derived_type::is_single_producer_single_consumer) + { + if (std::cmp_greater_equal(head, tail_.load(std::memory_order::relaxed) + rep().do_size())) + { + return false; + } + head_.store(head + 1, std::memory_order::relaxed); + } + else + { + while (true) + { + if (std::cmp_greater_equal(head, tail_.load(std::memory_order::relaxed) + rep().do_size())) + { + return false; + } + + if (head_.compare_exchange_weak(head, head + 1, std::memory_order::relaxed, std::memory_order::relaxed)) + [[likely]] + { + break; + } + } + } + + rep().do_push(head, std::forward(args)...); + return true; + } + + constexpr auto pop() noexcept -> auto + { + size_type tail; + if constexpr (derived_type::is_single_producer_single_consumer) + { + tail = tail_.load(std::memory_order::relaxed); + tail_.store(tail + 1, std::memory_order::relaxed); + } + else + { + constexpr auto order = derived_type::is_total_ordered ? std::memory_order::seq_cst : std::memory_order::relaxed; + tail = tail_.fetch_add(1, order); + } + + return rep().do_pop(tail); + } + + template + constexpr auto push(Args&&... args) noexcept -> void + { + size_type head; + if constexpr (derived_type::is_single_producer_single_consumer) + { + head = head_.load(std::memory_order::relaxed); + head_.store(head + 1, std::memory_order::relaxed); + } + else + { + constexpr auto order = derived_type::is_total_ordered ? std::memory_order::seq_cst : std::memory_order::relaxed; + head = head_.fetch_add(1, order); + } + + rep().do_push(head, std::forward(args)...); + } + + [[nodiscard]] auto size() const noexcept -> size_type + { + const auto head = head_.load(std::memory_order::relaxed); + const auto tail = tail_.load(std::memory_order::relaxed); + + // tail_ can be greater than head_ because of consumers doing pop, rather that try_pop, when the queue is empty. + if (head > tail) + [[likely]] + { + return head - tail; + } + + return 0; + } + + [[nodiscard]] auto capacity() const noexcept -> size_type + { + return rep().do_size(); + } + + [[nodiscard]] auto empty() const noexcept -> bool + { + const auto head = head_.load(std::memory_order::relaxed); + const auto tail = tail_.load(std::memory_order::relaxed); + + return head == tail; + } + + [[nodiscard]] auto full() const noexcept -> bool + { + return size() >= capacity(); + } + }; + } + + template< + typename T, + std::size_t ElementCount, + T NilValue = T{}, + bool MinimizeContention = true, + bool MaximizeThroughput = true, + bool IsTotalOrdered = false, + bool IsSingleProducerSingleConsumer = false + > + class FixedAtomicQueue : public queue_detail::QueueImpl> + { + using impl_type = queue_detail::QueueImpl; + friend impl_type; + + public: + using value_type = T; + using atomic_value_type = std::atomic; + + static_assert(atomic_value_type::is_always_lock_free, "Use `FixedQueue` instead"); + + using size_type = typename impl_type::size_type; + + constexpr static auto nil_value = NilValue; + constexpr static auto minimize_contention = MinimizeContention; + constexpr static auto maximize_throughput = MaximizeThroughput; + constexpr static auto is_total_ordered = IsTotalOrdered; + constexpr static auto is_single_producer_single_consumer = IsSingleProducerSingleConsumer; + + constexpr static auto element_count = minimize_contention ? std::bit_ceil(ElementCount) : ElementCount; + constexpr static auto elements_in_cache_line = queue_detail::cache_line_size / sizeof(atomic_value_type); + constexpr static auto shuffle_bits = queue_detail::index_shuffle_bits::value; + + private: + alignas(queue_detail::cache_line_size) atomic_value_type elements_[element_count]; + + constexpr auto do_pop(const size_type tail) noexcept -> value_type + { + auto& atomic_element = queue_detail::mapping(elements_, tail % element_count); + return impl_type::template do_pop_atomic(atomic_element); + } + + template + constexpr auto do_push(const size_type head, Args&&... args) noexcept -> void // + requires requires { value_type{std::forward(args)...}; } + { + auto& atomic_element = queue_detail::mapping(elements_, head % element_count); + impl_type::template do_push_atomic(atomic_element, value_type{std::forward(args)...}); + } + + template + constexpr auto do_push(const size_type head, U&& value) noexcept -> void // + requires requires { static_cast(std::forward(value)); } + { + auto& atomic_element = queue_detail::mapping(elements_, head % element_count); + impl_type::template do_push_atomic(atomic_element, static_cast(std::forward(value))); + } + + [[nodiscard]] constexpr auto do_size() const noexcept -> size_type + { + (void)this; + return element_count; + } + + public: + FixedAtomicQueue() noexcept + : impl_type{} + { + std::ranges::fill(elements_, nil_value); + } + + FixedAtomicQueue(const FixedAtomicQueue&) noexcept = delete; + auto operator=(const FixedAtomicQueue&) noexcept -> FixedAtomicQueue& = delete; + FixedAtomicQueue(FixedAtomicQueue&&) noexcept = default; + auto operator=(FixedAtomicQueue&&) noexcept -> FixedAtomicQueue& = default; + ~FixedAtomicQueue() noexcept = default; + }; + + template< + typename T, + std::size_t ElementCount, + bool MinimizeContention = true, + bool MaximizeThroughput = true, + bool IsTotalOrdered = false, + bool IsSingleProducerSingleConsumer = false + > + class FixedQueue : public queue_detail::QueueImpl> + { + using impl_type = queue_detail::QueueImpl; + friend impl_type; + + public: + using value_type = T; + + using state_type = typename impl_type::State; + using atomic_state_type = typename impl_type::atomic_state_type; + + using size_type = typename impl_type::size_type; + + constexpr static auto minimize_contention = MinimizeContention; + constexpr static auto maximize_throughput = MaximizeThroughput; + constexpr static auto is_total_ordered = IsTotalOrdered; + constexpr static auto is_single_producer_single_consumer = IsSingleProducerSingleConsumer; + + constexpr static auto element_count = minimize_contention ? std::bit_ceil(ElementCount) : ElementCount; + constexpr static auto elements_in_cache_line = queue_detail::cache_line_size / sizeof(atomic_state_type); + constexpr static auto shuffle_bits = queue_detail::index_shuffle_bits::value; + + private: + alignas(queue_detail::cache_line_size) atomic_state_type states_[element_count]; + alignas(queue_detail::cache_line_size) value_type elements_[element_count]; + + constexpr auto do_pop(const size_type tail) noexcept -> value_type + { + const auto index = queue_detail::mapping_index(tail % element_count); + return impl_type::template do_pop_atomic(states_[index], elements_[index]); + } + + template + constexpr auto do_push(const size_type head, Args&&... args) noexcept -> void // + requires requires { value_type{std::forward(args)...}; } + { + const auto index = queue_detail::mapping_index(head % element_count); + impl_type::do_push_atomic(states_[index], elements_[index], value_type{std::forward(args)...}); + } + + template + constexpr auto do_push(const size_type head, U&& value) noexcept -> void // + requires requires { static_cast(std::forward(value)); } + { + const auto index = queue_detail::mapping_index(head % element_count); + impl_type::do_push_atomic(states_[index], elements_[index], static_cast(std::forward(value))); + } + + [[nodiscard]] constexpr auto do_size() const noexcept -> size_type + { + (void)this; + return element_count; + } + + public: + FixedQueue() noexcept + : impl_type{} + { + std::ranges::fill(states_, state_type::EMPTY); + std::ranges::fill(elements_, value_type{}); + } + + FixedQueue(const FixedQueue&) noexcept = delete; + auto operator=(const FixedQueue&) noexcept -> FixedQueue& = delete; + FixedQueue(FixedQueue&&) noexcept = default; + auto operator=(FixedQueue&&) noexcept -> FixedQueue& = default; + ~FixedQueue() noexcept = default; + }; + + template< + typename T, + T NilValue = T{}, + typename Allocator = std::allocator, + bool MaximizeThroughput = true, + bool IsTotalOrdered = false, + bool IsSingleProducerSingleConsumer = false + > + class DynamicAtomicQueue : public queue_detail::QueueImpl> + { + using impl_type = queue_detail::QueueImpl; + friend impl_type; + + public: + using value_type = T; + using atomic_value_type = std::atomic; + + static_assert(atomic_value_type::is_always_lock_free, "Use `DynamicQueue` instead"); + + using size_type = typename impl_type::size_type; + + using allocator_type = Allocator; + using rebind_allocator_type = typename std::allocator_traits::template rebind_alloc; + + constexpr static auto nil_value = NilValue; + constexpr static auto maximize_throughput = MaximizeThroughput; + constexpr static auto is_total_ordered = IsTotalOrdered; + constexpr static auto is_single_producer_single_consumer = IsSingleProducerSingleConsumer; + + constexpr static auto elements_in_cache_line = queue_detail::cache_line_size / sizeof(atomic_value_type); + constexpr static auto shuffle_bits = queue_detail::index_shuffle_bits<0, elements_in_cache_line>::index_bits; + constexpr static auto min_size = queue_detail::index_shuffle_bits<0, elements_in_cache_line>::min_size; + + private: + alignas(queue_detail::cache_line_size) size_type size_; + atomic_value_type* elements_; + + GAL_PROMETHEUS_COMPILER_NO_UNIQUE_ADDRESS rebind_allocator_type allocator_; + + constexpr auto do_pop(const size_type tail) noexcept -> value_type + { + const auto mask = size_ - 1; + const auto index = tail & mask; + auto& atomic_element = queue_detail::mapping(elements_, index); + return impl_type::template do_pop_atomic(atomic_element); + } + + template + constexpr auto do_push(const size_type head, Args&&... args) noexcept -> void // + requires requires { value_type{std::forward(args)...}; } + { + const auto mask = size_ - 1; + const auto index = head & mask; + auto& atomic_element = queue_detail::mapping(elements_, index); + + impl_type::template do_push_atomic(atomic_element, value_type{std::forward(args)...}); + } + + template + constexpr auto do_push(const size_type head, U&& value) noexcept -> void // + requires requires { static_cast(std::forward(value)); } + { + const auto mask = size_ - 1; + const auto index = head & mask; + auto& atomic_element = queue_detail::mapping(elements_, index); + + impl_type::template do_push_atomic(atomic_element, static_cast(std::forward(value))); + } + + [[nodiscard]] constexpr auto do_size() const noexcept -> size_type + { + return size_; + } + + public: + explicit DynamicAtomicQueue(const size_type size, const allocator_type& allocator = allocator_type{}) noexcept + : size_{std::ranges::max(static_cast(min_size), std::bit_ceil(size))}, + elements_{nullptr}, + allocator_{allocator} + { + elements_ = allocator_.allocate(size_); + std::ranges::uninitialized_fill_n(elements_, size_, nil_value); + } + + DynamicAtomicQueue(const DynamicAtomicQueue&) noexcept = delete; + auto operator=(const DynamicAtomicQueue&) noexcept -> DynamicAtomicQueue& = delete; + DynamicAtomicQueue(DynamicAtomicQueue&& other) noexcept = default; + auto operator=(DynamicAtomicQueue&&) noexcept -> DynamicAtomicQueue& = default; + + ~DynamicAtomicQueue() noexcept + { + std::ranges::destroy_n(elements_, size_); + allocator_.deallocate(elements_, size_); + } + + auto swap(DynamicAtomicQueue& other) noexcept -> void + { + using std::swap; + + impl_type::swap(other); + swap(size_, other.size_); + swap(elements_, other.elements_); + swap(allocator_, other.allocator_); + } + + [[nodiscard]] auto get_allocator() const noexcept -> allocator_type + { + return allocator_; + } + }; + + template< + typename T, + typename Allocator = std::allocator, + bool MaximizeThroughput = true, + bool IsTotalOrdered = false, + bool IsSingleProducerSingleConsumer = false + > + class DynamicQueue : public queue_detail::QueueImpl> + { + using impl_type = queue_detail::QueueImpl; + friend impl_type; + + public: + using value_type = T; + + using state_type = typename impl_type::State; + using atomic_state_type = typename impl_type::atomic_state_type; + + using size_type = typename impl_type::size_type; + + using allocator_type = Allocator; + using rebind_allocator_type = typename std::allocator_traits::template rebind_alloc; + + constexpr static auto maximize_throughput = MaximizeThroughput; + constexpr static auto is_total_ordered = IsTotalOrdered; + constexpr static auto is_single_producer_single_consumer = IsSingleProducerSingleConsumer; + + constexpr static auto elements_in_cache_line = queue_detail::cache_line_size / sizeof(atomic_state_type); + constexpr static auto shuffle_bits = queue_detail::index_shuffle_bits<0, elements_in_cache_line>::index_bits; + constexpr static auto min_size = queue_detail::index_shuffle_bits<0, elements_in_cache_line>::min_size; + + private: + alignas(queue_detail::cache_line_size) size_type size_; + atomic_state_type* states_; + value_type* elements_; + + GAL_PROMETHEUS_COMPILER_NO_UNIQUE_ADDRESS rebind_allocator_type allocator_; + + constexpr auto do_pop(const size_type tail) noexcept -> value_type + { + const auto mask = size_ - 1; + const auto index = tail & mask; + const auto mapped_index = queue_detail::mapping_index(index); + return impl_type::template do_pop_atomic(states_[mapped_index], elements_[mapped_index]); + } + + template + constexpr auto do_push(const size_type head, Args&&... args) noexcept -> void // + requires requires { value_type{std::forward(args)...}; } + { + const auto mask = size_ - 1; + const auto index = head & mask; + const auto mapped_index = queue_detail::mapping_index(index); + impl_type::template do_push_atomic(states_[mapped_index], elements_[mapped_index], value_type{std::forward(args)...}); + } + + template + constexpr auto do_push(const size_type head, U&& value) noexcept -> void // + requires requires { static_cast(std::forward(value)); } + { + const auto mask = size_ - 1; + const auto index = head & mask; + const auto mapped_index = queue_detail::mapping_index(index); + impl_type::template do_push_atomic(states_[mapped_index], elements_[mapped_index], static_cast(std::forward(value))); + } + + [[nodiscard]] constexpr auto do_size() const noexcept -> size_type + { + return size_; + } + + template + [[nodiscard]] constexpr auto allocate(const size_type count) noexcept -> U* + { + static_assert(sizeof(typename rebind_allocator_type::value_type) == 1); + return reinterpret_cast(std::allocator_traits::allocate(allocator_, count * sizeof(U))); + } + + template + constexpr auto deallocate(U* p, const size_type count) noexcept -> void + { + static_assert(sizeof(typename rebind_allocator_type::value_type) == 1); + std::allocator_traits::deallocate(allocator_, reinterpret_cast(p), count * sizeof(U)); + } + + public: + explicit DynamicQueue(const size_type size, const allocator_type& allocator = allocator_type{}) noexcept + : size_{std::ranges::max(static_cast(min_size), std::bit_ceil(size))}, + states_{nullptr}, + elements_{nullptr}, + allocator_{allocator} + { + states_ = DynamicQueue::allocate(size_); + elements_ = DynamicQueue::allocate(size_); + std::ranges::uninitialized_fill_n(states_, size_, state_type::EMPTY); + std::ranges::uninitialized_fill_n(elements_, size_, value_type{}); + } + + DynamicQueue(const DynamicQueue&) noexcept = delete; + auto operator=(const DynamicQueue&) noexcept -> DynamicQueue& = delete; + DynamicQueue(DynamicQueue&& other) noexcept = default; + auto operator=(DynamicQueue&&) noexcept -> DynamicQueue& = default; + + ~DynamicQueue() noexcept + { + std::ranges::destroy_n(states_, size_); + std::ranges::destroy_n(elements_, size_); + DynamicQueue::deallocate(states_, size_); + DynamicQueue::deallocate(elements_, size_); + } + + auto swap(DynamicQueue& other) noexcept -> void + { + using std::swap; + + impl_type::swap(other); + swap(size_, other.size_); + swap(elements_, other.elements_); + swap(allocator_, other.allocator_); + } + + [[nodiscard]] auto get_allocator() const noexcept -> allocator_type + { + return allocator_; + } + }; +} diff --git a/src/concurrency/thread.cpp b/src/concurrency/thread.cpp new file mode 100644 index 00000000..ed46f5aa --- /dev/null +++ b/src/concurrency/thread.cpp @@ -0,0 +1,176 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include +#include +#include +#include + +#include + +#include + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +#if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) + +#include + +#elif defined(GAL_PROMETHEUS_PLATFORM_LINUX) + +#include +#include +#include + +// this_thread::get_id ==> syscall(SYS_gettid) +// #include + +#elif defined(GAL_PROMETHEUS_PLATFORM_DARWIN) + +#include + +#else + +#error "Unsupported platform" + +#endif + +namespace +{ + using gal::prometheus::concurrency::thread_id; + + std::mutex thread_names_mutex; + std::unordered_map thread_names; +} + +namespace gal::prometheus::concurrency +{ + namespace this_process + { + [[nodiscard]] auto get_id() noexcept -> process_id + { + #if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) + + // return GetCurrentProcessId(); + + #if defined(_WIN64) + // win64 => 0x40 + constexpr DWORD nt_thread_information_block_current_process_id = 0x40; + return __readgsdword(nt_thread_information_block_current_process_id); + #else + // win32 => 0x20 + constexpr DWORD nt_thread_information_block_current_process_id = 0x20; + return __readfsdword(nt_thread_information_block_current_process_id); + #endif + + #elif defined(GAL_PROMETHEUS_PLATFORM_LINUX) or defined(GAL_PROMETHEUS_PLATFORM_DARWIN) + + return static_cast(getpid()); + + #else + + GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); + + #endif + } + } + + namespace this_thread + { + [[nodiscard]] auto get_id() noexcept -> thread_id + { + #if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) + + // return GetCurrentThreadId(); + + #if defined(_WIN64) + // win64 => 0x48 + constexpr DWORD nt_thread_information_block_current_thread_id = 0x48; + return __readgsdword(nt_thread_information_block_current_thread_id); + #else + // win32 => 0x24 + constexpr DWORD nt_thread_information_block_current_thread_id = 0x24; + return __readfsdword(nt_thread_information_block_current_thread_id); + #endif + + #elif defined(GAL_PROMETHEUS_PLATFORM_LINUX) + + return static_cast(pthread_self()); + + #elif defined(GAL_PROMETHEUS_PLATFORM_DARWIN) + + uint64_t tid; + pthread_threadid_np(nullptr, &tid); + return static_cast(tid); + + #else + + GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); + + #endif + } + + auto set_name(const std::string_view name) noexcept -> void + { + const auto id = get_id(); + + #if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) + + // fixme: UTF8 => UTF16 + const auto path = std::filesystem::path{name}; + const auto real_name = path.string(); + + const auto result = SetThreadDescription(GetCurrentThread(), path.c_str()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(SUCCEEDED(result)); + + #elif defined(GAL_PROMETHEUS_PLATFORM_LINUX) + + // fixme: truncated? + const auto real_name = name; + + const auto result = pthread_setname_np(id, name.data()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(result == 0); + + #elif defined(GAL_PROMETHEUS_PLATFORM_DARWIN) + + // fixme: truncated? + const auto real_name = name; + + const auto result = pthread_setname_np(name.data()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(result == 0); + + #else + + GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); + + #endif + + const auto lock = std::scoped_lock{thread_names_mutex}; + + if (const auto it = thread_names.find(id); + it != thread_names.end()) + { + it->second = real_name; + } + else + { + thread_names.emplace_hint(it, id, real_name); + } + } + + auto get_name(const thread_id id) noexcept -> std::optional + { + const auto lock = std::scoped_lock{thread_names_mutex}; + + if (const auto it = thread_names.find(id); + it != thread_names.end()) + { + return it->second; + } + + return std::nullopt; + } + } +} diff --git a/src/concurrency/thread.ixx b/src/concurrency/thread.hpp similarity index 70% rename from src/concurrency/thread.ixx rename to src/concurrency/thread.hpp index b3c57e8d..f66e8dac 100644 --- a/src/concurrency/thread.ixx +++ b/src/concurrency/thread.hpp @@ -1,18 +1,8 @@ // This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal +// Copyright (C) 2022-2025 Life4gal // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.concurrency:thread; - -import std; - -#else #pragma once #include @@ -21,9 +11,7 @@ import std; #include -#endif - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::concurrency) +namespace gal::prometheus::concurrency { using thread_id = std::uint32_t; using process_id = std::uint32_t; diff --git a/src/concurrency/thread.impl.ixx b/src/concurrency/thread.impl.ixx deleted file mode 100644 index 4c5c1232..00000000 --- a/src/concurrency/thread.impl.ixx +++ /dev/null @@ -1,123 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include -#if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) -#include -#else - -#endif - -export module gal.prometheus.concurrency:thread.impl; - -import std; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -import :thread; - -#else -#include -#include -#include - -#if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) -#include -#else - -#endif - -#include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -#endif - -namespace -{ - using gal::prometheus::concurrency::thread_id; - - std::mutex thread_names_mutex; - std::unordered_map thread_names; -} - -namespace gal::prometheus::concurrency -{ - namespace this_process - { - [[nodiscard]] auto get_id() noexcept -> process_id - { - #if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) - // return GetCurrentProcessId(); - - #if defined(_WIN64) - // win64 => 0x40 - constexpr DWORD nt_thread_information_block_current_process_id = 0x40; - return __readgsdword(nt_thread_information_block_current_process_id); - #else - #error "Architecture Not Supported" - // win32 => 0x20 - constexpr DWORD nt_thread_information_block_current_process_id = 0x20; - return __readfsdword(nt_thread_information_block_current_process_id); - #endif - - #else - GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); - #endif - } - } - - namespace this_thread - { - [[nodiscard]] auto get_id() noexcept -> thread_id - { - #if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) - // return GetCurrentThreadId(); - - #if defined(_WIN64) - // win64 => 0x48 - constexpr DWORD nt_thread_information_block_current_thread_id = 0x48; - return __readgsdword(nt_thread_information_block_current_thread_id); - #else - // win32 => 0x24 - #error "Architecture Not Supported" - constexpr DWORD nt_thread_information_block_current_thread_id = 0x24; - return __readfsdword(nt_thread_information_block_current_thread_id); - #endif - - #else - GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); - #endif - } - - auto set_name(const std::string_view name) noexcept -> void - { - #if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) - // fixme - const auto n = std::filesystem::path{name}; - const auto result = SetThreadDescription(GetCurrentThread(), n.c_str()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(SUCCEEDED(result)); - - const auto lock = std::scoped_lock{thread_names_mutex}; - const auto id = get_id(); - if (const auto it = thread_names.find(id); - it != thread_names.end()) { it->second = n.string(); } - else { thread_names.emplace_hint(it, id, n.string()); } - #else - GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); - #endif - } - - auto get_name(const thread_id id) noexcept -> std::optional - { - const auto lock = std::scoped_lock{thread_names_mutex}; - if (const auto it = thread_names.find(id); it != thread_names.end()) { return it->second; } - - return std::nullopt; - } - } // namespace this_thread -} // namespace gal::prometheus::concurrency diff --git a/src/concurrency/unfair_mutex.impl.ixx b/src/concurrency/unfair_mutex.impl.ixx index 68580a2b..dcda680a 100644 --- a/src/concurrency/unfair_mutex.impl.ixx +++ b/src/concurrency/unfair_mutex.impl.ixx @@ -3,24 +3,30 @@ // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#if GAL_PROMETHEUS_USE_MODULE -module; +#if not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED #include -export module gal.prometheus.concurrency:unfair_mutex.impl; +export module gal.prometheus:concurrency.unfair_mutex.impl; import std; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE -import :unfair_mutex; +#if GAL_PROMETHEUS_COMPILER_DEBUG +import :platform; +#endif + +import :concurrency.unfair_mutex; + +#endif not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED + +#if not GAL_PROMETHEUS_USE_MODULE -#else #include #include #include #include + #include #include GAL_PROMETHEUS_ERROR_DEBUG_MODULE @@ -147,7 +153,7 @@ namespace } } -namespace gal::prometheus::concurrency +GAL_PROMETHEUS_COMPILER_MODULE_NAMESPACE_EXPORT_IMPL(concurrency) { auto UnfairMutex::holds_invariant() const noexcept -> bool { diff --git a/src/concurrency/unfair_mutex.ixx b/src/concurrency/unfair_mutex.ixx index b0e120e5..a4107259 100644 --- a/src/concurrency/unfair_mutex.ixx +++ b/src/concurrency/unfair_mutex.ixx @@ -3,31 +3,40 @@ // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#if GAL_PROMETHEUS_USE_MODULE -module; +#if not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED #include -export module gal.prometheus.concurrency:unfair_mutex; +export module gal.prometheus:concurrency.unfair_mutex; import std; -import gal.prometheus.meta; -import :thread; +import :meta; + +import :concurrency.thread; + +#endif not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED + +#if not GAL_PROMETHEUS_USE_MODULE -#else #pragma once #include #include #include -#include + #include +#include + #endif -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::concurrency) +#if GAL_PROMETHEUS_INTELLISENSE_WORKING +namespace GAL_PROMETHEUS_COMPILER_MODULE_NAMESPACE_PREFIX :: concurrency +#else +GAL_PROMETHEUS_COMPILER_MODULE_NAMESPACE_EXPORT(concurrency) +#endif { class UnfairMutex final { @@ -112,7 +121,7 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::concurrency) }; } // namespace gal::prometheus::concurrency -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE_STD +GAL_PROMETHEUS_COMPILER_MODULE_NAMESPACE_STD { template<> struct formatter diff --git a/src/coroutine/coroutine.hpp b/src/coroutine/coroutine.hpp new file mode 100644 index 00000000..acea44ea --- /dev/null +++ b/src/coroutine/coroutine.hpp @@ -0,0 +1,9 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include diff --git a/src/coroutine/coroutine.ixx b/src/coroutine/coroutine.ixx deleted file mode 100644 index b5d400d0..00000000 --- a/src/coroutine/coroutine.ixx +++ /dev/null @@ -1,18 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -export module gal.prometheus.coroutine; - -export import :task; -export import :generator; - -#else -#pragma once - -#include -#include - -#endif diff --git a/src/coroutine/generator.hpp b/src/coroutine/generator.hpp new file mode 100644 index 00000000..9265f27b --- /dev/null +++ b/src/coroutine/generator.hpp @@ -0,0 +1,202 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include + +#include + +namespace gal::prometheus::coroutine +{ + template + requires(not std::is_void_v) + class Generator; + + namespace generator_detail + { + template + class GeneratorPromise final + { + public: + using return_type = ReturnType; + using pointer = std::add_pointer_t; + + using generator_type = Generator; + + using promise_type = GeneratorPromise; + using coroutine_handle = std::coroutine_handle; + + private: + pointer value_{nullptr}; + std::exception_ptr exception_{nullptr}; + + public: + constexpr auto initial_suspend() const noexcept -> std::suspend_always // NOLINT(modernize-use-nodiscard) + { + (void)this; + return {}; + } + + [[nodiscard]] constexpr auto final_suspend() const noexcept -> std::suspend_always + { + (void)this; + return {}; + } + + template + constexpr auto await_transform(U&&) -> std::suspend_never = delete; + + constexpr auto yield_value(return_type& value) noexcept -> std::suspend_always + { + value_ = std::addressof(value); + return {}; + } + + constexpr auto yield_value(return_type&& value) noexcept -> std::suspend_always + { + value_ = std::addressof(value); + return {}; + } + + [[nodiscard]] constexpr auto get_return_object() noexcept -> generator_type { return generator_type{coroutine_handle::from_promise(*this)}; } + + constexpr auto unhandled_exception() noexcept -> void { exception_ = std::current_exception(); } + + constexpr auto return_void() const noexcept -> void { (void)this; } + + [[nodiscard]] constexpr auto has_exception() const noexcept -> bool { return exception_.operator bool(); } + + constexpr auto rethrow_if_exception() const -> void { if (has_exception()) { std::rethrow_exception(exception_); } } + + constexpr auto result() const & -> const ReturnType& + { + rethrow_if_exception(); + + return *value_; + } + + constexpr auto result() && -> ReturnType&& + { + rethrow_if_exception(); + + return std::move(*value_); + } + }; + + using iterator_sentinel = std::default_sentinel_t; + + template + class Iterator + { + using generator_type = Generator; + + public: + using iterator_concept = std::input_iterator_tag; + using iterator_category = std::input_iterator_tag; + + using value_type = typename generator_type::return_type; + using difference_type = typename generator_type::difference_type; + using pointer = typename generator_type::pointer; + using reference = typename generator_type::reference; + + private: + using promise_type = GeneratorPromise; + using coroutine_handle = typename promise_type::coroutine_handle; + + coroutine_handle coroutine_; + + public: + constexpr explicit Iterator(coroutine_handle coroutine = coroutine_handle{}) noexcept + : coroutine_{coroutine} {} + + [[nodiscard]] friend constexpr auto operator==(const Iterator& lhs, iterator_sentinel) noexcept -> bool + { + return lhs.coroutine_ == nullptr or lhs.coroutine_.done(); + } + + [[nodiscard]] friend constexpr auto operator==(iterator_sentinel, const Iterator& rhs) noexcept -> bool { return rhs == iterator_sentinel{}; } + + constexpr auto operator++() -> Iterator& + { + coroutine_.resume(); + if (coroutine_.done()) { coroutine_.promise().rethrow_if_exception(); } + + return *this; + } + + constexpr auto operator++(int) -> void { operator++(); } + + [[nodiscard]] constexpr auto operator*() const & -> decltype(auto) { return coroutine_.promise().result(); } + + [[nodiscard]] constexpr auto operator*() && noexcept -> decltype(auto) { return std::move(coroutine_.promise()).result(); } + + [[nodiscard]] constexpr auto operator->() const & noexcept -> decltype(auto) { return std::addressof(operator*()); } + + [[nodiscard]] constexpr auto operator->() && noexcept -> decltype(auto) { return std::addressof(std::move(*this).operator*()); } + }; + } + + template + requires(not std::is_void_v) + class Generator + { + public: + using return_type = std::remove_reference_t; + using difference_type = std::ptrdiff_t; + using pointer = std::add_pointer_t; + using reference = std::add_lvalue_reference_t; + using iterator = generator_detail::Iterator>; + + using promise_type = generator_detail::GeneratorPromise; + using coroutine_handle = typename promise_type::coroutine_handle; + + private: + friend promise_type; + + coroutine_handle coroutine_; + + constexpr explicit Generator(coroutine_handle coroutine) noexcept + : coroutine_{coroutine} {} + + public: + constexpr Generator() noexcept + : coroutine_{nullptr} {} + + constexpr Generator(const Generator&) noexcept = delete; + constexpr auto operator=(const Generator&) noexcept -> Generator& = delete; + + constexpr Generator(Generator&& other) noexcept + : coroutine_{std::exchange(other.coroutine_, nullptr)} {} + + constexpr auto operator=(Generator&& other) noexcept -> Generator& + { + coroutine_ = std::exchange(other.coroutine_, nullptr); + + return *this; + } + + constexpr ~Generator() noexcept { if (coroutine_) { coroutine_.destroy(); } } + + [[nodiscard]] constexpr auto begin() noexcept -> iterator + { + // If coroutine_ is nullptr, this is equivalent to returning `end()` + iterator it{coroutine_}; + + if (coroutine_) { ++it; } + + return it; + } + + [[nodiscard]] constexpr auto end() const noexcept -> generator_detail::iterator_sentinel + { + (void)this; + return {}; + } + }; +} diff --git a/src/coroutine/generator.ixx b/src/coroutine/generator.ixx deleted file mode 100644 index e27e34e5..00000000 --- a/src/coroutine/generator.ixx +++ /dev/null @@ -1,211 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -export module gal.prometheus.coroutine:generator; - -import std; - -#else -#pragma once - -#include -#include -#include -#include - -#include - -#endif - -namespace gal::prometheus::coroutine -{ - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - template - requires(not std::is_void_v) - class Generator; - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END - - template - class GeneratorPromise final - { - public: - using return_type = ReturnType; - using pointer = std::add_pointer_t; - - using generator_type = Generator; - - using promise_type = GeneratorPromise; - using coroutine_handle = std::coroutine_handle; - - private: - pointer value_{nullptr}; - std::exception_ptr exception_{nullptr}; - - public: - [[nodiscard]] constexpr auto initial_suspend() const noexcept -> std::suspend_always - { - (void)this; - return {}; - } - - [[nodiscard]] constexpr auto final_suspend() const noexcept -> std::suspend_always - { - (void)this; - return {}; - } - - template - constexpr auto await_transform(U&&) -> std::suspend_never = delete; - - constexpr auto yield_value(return_type& value) noexcept -> std::suspend_always - { - value_ = std::addressof(value); - return {}; - } - - constexpr auto yield_value(return_type&& value) noexcept -> std::suspend_always - { - value_ = std::addressof(value); - return {}; - } - - [[nodiscard]] constexpr auto get_return_object() noexcept -> generator_type { return generator_type{coroutine_handle::from_promise(*this)}; } - - constexpr auto unhandled_exception() noexcept -> void { exception_ = std::current_exception(); } - - constexpr auto return_void() const noexcept -> void { (void)this; } - - [[nodiscard]] constexpr auto has_exception() const noexcept -> bool { return exception_.operator bool(); } - - constexpr auto rethrow_if_exception() const -> void { if (has_exception()) { std::rethrow_exception(exception_); } } - - constexpr auto result() const & -> const ReturnType& - { - rethrow_if_exception(); - - return *value_; - } - - constexpr auto result() && -> ReturnType&& - { - rethrow_if_exception(); - - return std::move(*value_); - } - }; - - struct iterator_sentinel {}; - - template - class Iterator - { - using generator_type = Generator; - - public: - using iterator_concept = std::input_iterator_tag; - using iterator_category = std::input_iterator_tag; - - using value_type = typename generator_type::return_type; - using difference_type = typename generator_type::difference_type; - using pointer = typename generator_type::pointer; - using reference = typename generator_type::reference; - - private: - using promise_type = GeneratorPromise; - using coroutine_handle = typename promise_type::coroutine_handle; - - coroutine_handle coroutine_; - - public: - constexpr explicit Iterator(coroutine_handle coroutine = coroutine_handle{}) noexcept - : coroutine_{coroutine} {} - - [[nodiscard]] friend constexpr auto operator==(const Iterator& lhs, iterator_sentinel) noexcept -> bool - { - return lhs.coroutine_ == nullptr or lhs.coroutine_.done(); - } - - [[nodiscard]] friend constexpr auto operator==(iterator_sentinel, const Iterator& rhs) noexcept -> bool { return rhs == iterator_sentinel{}; } - - constexpr auto operator++() -> Iterator& - { - coroutine_.resume(); - if (coroutine_.done()) { coroutine_.promise().rethrow_if_exception(); } - - return *this; - } - - constexpr auto operator++(int) -> void { operator++(); } - - [[nodiscard]] constexpr auto operator*() const & -> decltype(auto) { return coroutine_.promise().result(); } - - [[nodiscard]] constexpr auto operator*() && noexcept -> decltype(auto) { return std::move(coroutine_.promise()).result(); } - - [[nodiscard]] constexpr auto operator->() const & noexcept -> decltype(auto) { return std::addressof(operator*()); } - - [[nodiscard]] constexpr auto operator->() && noexcept -> decltype(auto) { return std::addressof(std::move(*this).operator*()); } - }; - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - template - requires(not std::is_void_v) - class Generator - { - public: - using return_type = std::remove_reference_t; - using difference_type = std::ptrdiff_t; - using pointer = std::add_pointer_t; - using reference = std::add_lvalue_reference_t; - using iterator = Iterator>; - - using promise_type = GeneratorPromise; - using coroutine_handle = typename promise_type::coroutine_handle; - - private: - friend promise_type; - - coroutine_handle coroutine_; - - constexpr explicit Generator(coroutine_handle coroutine) noexcept - : coroutine_{coroutine} {} - - public: - constexpr Generator() noexcept - : coroutine_{nullptr} {} - - constexpr Generator(const Generator&) noexcept = delete; - constexpr auto operator=(const Generator&) noexcept -> Generator& = delete; - - constexpr Generator(Generator&& other) noexcept - : coroutine_{std::exchange(other.coroutine_, nullptr)} {} - - constexpr auto operator=(Generator&& other) noexcept -> Generator& - { - coroutine_ = std::exchange(other.coroutine_, nullptr); - - return *this; - } - - constexpr ~Generator() noexcept { if (coroutine_) { coroutine_.destroy(); } } - - [[nodiscard]] constexpr auto begin() noexcept -> iterator - { - // If coroutine_ is nullptr, this is equivalent to returning `end()` - iterator it{coroutine_}; - - if (coroutine_) { ++it; } - - return it; - } - - [[nodiscard]] constexpr auto end() const noexcept -> iterator_sentinel - { - (void)this; - return {}; - } - }; - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -} // namespace gal::prometheus::coroutine diff --git a/src/coroutine/task.hpp b/src/coroutine/task.hpp new file mode 100644 index 00000000..ff7678a2 --- /dev/null +++ b/src/coroutine/task.hpp @@ -0,0 +1,226 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include + +#include + +namespace gal::prometheus::coroutine +{ + template + class Task; + + namespace task_detail + { + template + class TaskPromise; + + template + class CoroutineReturn + { + public: + using return_type = ReturnType; + + private: + return_type value_; + + public: + constexpr auto return_value(ReturnType value) noexcept -> void { value_ = std::move(value); } + + template + requires std::is_constructible_v + constexpr auto return_value(Args&&... args) noexcept(std::is_nothrow_constructible_v) -> void + { + value_ = return_type{std::forward(args)...}; + } + + constexpr auto result() & -> ReturnType& + { + if (auto& self = *static_cast*>(this); + self.exception_) { std::rethrow_exception(self.exception_); } + return value_; + } + + constexpr auto result() && -> ReturnType&& + { + if (auto& self = *static_cast*>(this); + self.exception_) { std::rethrow_exception(self.exception_); } + return std::move(value_); + } + }; + + template<> + class CoroutineReturn + { + public: + constexpr auto return_void() const noexcept -> void { (void)this; } + + /* constexpr */ + auto result() -> void; + }; + + template + class TaskPromise final : public CoroutineReturn + { + friend CoroutineReturn; + + public: + using return_type = ReturnType; + using task_type = Task; + using promise_type = TaskPromise; + using coroutine_handle = std::coroutine_handle; + using continuation_type = std::coroutine_handle<>; + + struct awaitable + { + [[nodiscard]] constexpr auto await_ready() const noexcept -> bool + { + (void)this; + return false; + } + + template + [[nodiscard]] constexpr auto await_suspend(std::coroutine_handle coroutine) noexcept -> continuation_type + { + auto& promise = coroutine.promise(); + return promise.continuation_ ? promise.continuation_ : std::noop_coroutine(); + } + + constexpr auto await_resume() const noexcept -> void { (void)this; } + }; + + private: + continuation_type continuation_{nullptr}; + std::exception_ptr exception_{nullptr}; + + public: + constexpr auto initial_suspend() const noexcept -> std::suspend_always // NOLINT(modernize-use-nodiscard) + { + (void)this; + return {}; + } + + [[nodiscard]] constexpr auto final_suspend() noexcept -> awaitable + { + (void)this; + return {}; + } + + [[nodiscard]] constexpr auto get_return_object() noexcept -> task_type { return task_type{coroutine_handle::from_promise(*this)}; } + + constexpr auto unhandled_exception() noexcept -> void { exception_ = std::current_exception(); } + + constexpr auto continuation(const continuation_type continuation) noexcept -> void { continuation_ = continuation; } + + [[nodiscard]] constexpr auto has_exception() const noexcept -> bool { return exception_.operator bool(); } + }; + + /* constexpr */ + inline auto CoroutineReturn::result() -> void + { + if (const auto& self = *static_cast*>(this); // NOLINT + self.exception_) { std::rethrow_exception(self.exception_); } + } + } + + template + class [[nodiscard]] Task final + { + public: + using promise_type = task_detail::TaskPromise; + using task_type = Task; + + using return_type = typename promise_type::return_type; + using coroutine_handle = std::coroutine_handle; + using continuation_type = std::coroutine_handle<>; + + struct awaitable + { + coroutine_handle coroutine; + + [[nodiscard]] constexpr auto await_ready() const noexcept -> bool { return not coroutine or coroutine.done(); } + + template + [[nodiscard]] constexpr auto await_suspend(std::coroutine_handle continuation) noexcept -> continuation_type + { + coroutine.promise().continuation(continuation); + return coroutine; + } + + [[nodiscard]] constexpr auto await_resume() & -> decltype(auto) { return coroutine.promise().result(); } + + constexpr auto await_resume() && -> decltype(auto) { return std::move(coroutine.promise()).result(); } + }; + + private: + coroutine_handle coroutine_; + + public: + constexpr Task(const Task&) noexcept = delete; + constexpr auto operator=(const Task&) noexcept -> Task& = delete; + + constexpr explicit Task() noexcept + : coroutine_{nullptr} {} + + constexpr explicit Task(coroutine_handle handle) noexcept + : coroutine_{handle} {} + + constexpr Task(Task&& other) noexcept + : coroutine_{std::exchange(other.coroutine_, nullptr)} {} + + constexpr auto operator=(Task&& other) noexcept -> Task& + { + if (std::addressof(other) != this) + { + if (coroutine_) { coroutine_.destroy(); } + + coroutine_ = std::exchange(other.coroutine_, nullptr); + } + + return *this; + } + + constexpr ~Task() noexcept { if (coroutine_) { coroutine_.destroy(); } } + + [[nodiscard]] constexpr auto promise() & noexcept -> promise_type& { return coroutine_.promise(); } + + [[nodiscard]] constexpr auto promise() const & noexcept -> const promise_type& { return coroutine_.promise(); } + + [[nodiscard]] constexpr auto promise() && noexcept -> promise_type&& { return std::move(coroutine_.promise()); } + + [[nodiscard]] constexpr auto handle() -> coroutine_handle { return coroutine_; } + + [[nodiscard]] constexpr auto done() const noexcept -> bool { return not coroutine_ or coroutine_.done(); } + + constexpr auto operator()() const -> bool { return this->resume(); } + + constexpr auto resume() -> bool + { + if (not coroutine_.done()) { coroutine_.resume(); } + return not coroutine_.done(); + } + + [[nodiscard]] constexpr auto destroy() const noexcept -> bool + { + if (coroutine_) + { + coroutine_.destroy(); + coroutine_ = nullptr; + return true; + } + + return false; + } + + constexpr auto operator co_await() & noexcept -> awaitable { return {.coroutine = coroutine_}; } + + constexpr auto operator co_await() && noexcept -> awaitable { return {.coroutine = coroutine_}; } + }; +} diff --git a/src/coroutine/task.ixx b/src/coroutine/task.ixx deleted file mode 100644 index 0107e882..00000000 --- a/src/coroutine/task.ixx +++ /dev/null @@ -1,238 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -export module gal.prometheus.coroutine:task; - -import std; - -#else -#pragma once - -#include -#include -#include -#include - -#include - -#endif - -namespace gal::prometheus::coroutine -{ - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - template - class Task; - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END - - template - class P - { - public: - using return_type = ReturnType; - - private: - return_type value_; - - public: - constexpr auto return_value(ReturnType value) noexcept -> void { value_ = std::move(value); } - - template - requires std::is_constructible_v - constexpr auto return_value(Args&&... args) noexcept(std::is_nothrow_constructible_v) -> void - { - value_ = return_type{std::forward(args)...}; - } - - constexpr auto result() & -> ReturnType&; - - constexpr auto result() && -> ReturnType&&; - }; - - template<> - class P - { - public: - constexpr auto return_void() const noexcept -> void { (void)this; } - - /* constexpr */ - auto result() -> void; - }; - - template - class TaskPromise final : public P - { - friend P; - - public: - using return_type = ReturnType; - using task_type = Task; - using promise_type = TaskPromise; - using coroutine_handle = std::coroutine_handle; - using continuation_type = std::coroutine_handle<>; - - struct awaitable - { - [[nodiscard]] constexpr auto await_ready() const noexcept -> bool - { - (void)this; - return false; - } - - template - [[nodiscard]] constexpr auto await_suspend(std::coroutine_handle coroutine) noexcept -> continuation_type - { - auto& promise = coroutine.promise(); - return promise.continuation_ ? promise.continuation_ : std::noop_coroutine(); - } - - constexpr auto await_resume() const noexcept -> void { (void)this; } - }; - - private: - continuation_type continuation_{nullptr}; - std::exception_ptr exception_{nullptr}; - - public: - [[nodiscard]] constexpr auto initial_suspend() const noexcept -> std::suspend_always - { - (void)this; - return {}; - } - - [[nodiscard]] constexpr auto final_suspend() noexcept -> awaitable - { - (void)this; - return {}; - } - - [[nodiscard]] constexpr auto get_return_object() noexcept -> task_type { return task_type{coroutine_handle::from_promise(*this)}; } - - constexpr auto unhandled_exception() noexcept -> void { exception_ = std::current_exception(); } - - constexpr auto continuation(const continuation_type continuation) noexcept -> void { continuation_ = continuation; } - - [[nodiscard]] constexpr auto has_exception() const noexcept -> bool { return exception_.operator bool(); } - }; - - template - constexpr auto P::result() & -> ReturnType& - { - if (auto& self = *static_cast*>(this); - self.exception_) { std::rethrow_exception(self.exception_); } - return value_; - } - - template - constexpr auto P::result() && -> ReturnType&& - { - if (auto& self = *static_cast*>(this); - self.exception_) { std::rethrow_exception(self.exception_); } - return std::move(value_); - } - - /* constexpr */ - inline auto P::result() -> void - { - if (const auto& self = *static_cast*>(this); // NOLINT - self.exception_) { std::rethrow_exception(self.exception_); } - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - template - class [[nodiscard]] Task final - { - public: - using promise_type = TaskPromise; - using task_type = Task; - - using return_type = typename promise_type::return_type; - using coroutine_handle = std::coroutine_handle; - using continuation_type = std::coroutine_handle<>; - - struct awaitable - { - coroutine_handle coroutine; - - [[nodiscard]] constexpr auto await_ready() const noexcept -> bool { return not coroutine or coroutine.done(); } - - template - [[nodiscard]] constexpr auto await_suspend(std::coroutine_handle continuation) noexcept -> continuation_type - { - coroutine.promise().continuation(continuation); - return coroutine; - } - - [[nodiscard]] constexpr auto await_resume() & -> decltype(auto) { return coroutine.promise().result(); } - - constexpr auto await_resume() && -> decltype(auto) { return std::move(coroutine.promise()).result(); } - }; - - private: - coroutine_handle coroutine_; - - public: - constexpr Task(const Task&) noexcept = delete; - constexpr auto operator=(const Task&) noexcept -> Task& = delete; - - constexpr explicit Task() noexcept - : coroutine_{nullptr} {} - - constexpr explicit Task(coroutine_handle handle) noexcept - : coroutine_{handle} {} - - constexpr Task(Task&& other) noexcept - : coroutine_{std::exchange(other.coroutine_, nullptr)} {} - - constexpr auto operator=(Task&& other) noexcept -> Task& - { - if (std::addressof(other) != this) - { - if (coroutine_) { coroutine_.destroy(); } - - coroutine_ = std::exchange(other.coroutine_, nullptr); - } - - return *this; - } - - constexpr ~Task() noexcept { if (coroutine_) { coroutine_.destroy(); } } - - [[nodiscard]] constexpr auto promise() & noexcept -> promise_type& { return coroutine_.promise(); } - - [[nodiscard]] constexpr auto promise() const & noexcept -> const promise_type& { return coroutine_.promise(); } - - [[nodiscard]] constexpr auto promise() && noexcept -> promise_type&& { return std::move(coroutine_.promise()); } - - [[nodiscard]] constexpr auto handle() -> coroutine_handle { return coroutine_; } - - [[nodiscard]] constexpr auto done() const noexcept -> bool { return not coroutine_ or coroutine_.done(); } - - constexpr auto operator()() const -> bool { return this->resume(); } - - constexpr auto resume() -> bool - { - if (not coroutine_.done()) { coroutine_.resume(); } - return not coroutine_.done(); - } - - [[nodiscard]] constexpr auto destroy() const noexcept -> bool - { - if (coroutine_) - { - coroutine_.destroy(); - coroutine_ = nullptr; - return true; - } - - return false; - } - - constexpr auto operator co_await() & noexcept -> awaitable { return {.coroutine = coroutine_}; } - - constexpr auto operator co_await() && noexcept -> awaitable { return {.coroutine = coroutine_}; } - }; - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -} // namespace gal::prometheus::coroutine diff --git a/src/draw/context.cpp b/src/draw/context.cpp new file mode 100644 index 00000000..e55d3411 --- /dev/null +++ b/src/draw/context.cpp @@ -0,0 +1,352 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +namespace {} + +namespace gal::prometheus::draw +{ + auto Context::find_window(const Window& window) const noexcept -> index_type + { + const auto it = std::ranges::find( + windows_ | std::views::reverse, + std::addressof(window), + [](const auto& w) noexcept -> const auto* { + return std::addressof(w); + } + ); + + const auto it_base = it.base(); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it_base != std::ranges::end(windows_)); + return std::ranges::distance(std::ranges::begin(windows_), it_base); + } + + Context::Context() noexcept + : + // default_draw_list_shared_data_{}, + // font_default_{}, + theme_default_{Theme::default_theme()}, + draw_list_shared_data_stack_{}, + font_stack_{}, + theme_stack_{}, + draw_list_shared_data_current_{stack_use_default}, + font_current_{stack_use_default}, + theme_current_{stack_use_default}, + pad_{0}, + mouse_{.3f, 36}, + window_current_{nullptr}, + window_hovered_{nullptr}, + widget_id_hovered_{invalid_id}, + widget_id_activated_{invalid_id} + { + std::ignore = pad_; + } + + auto Context::instance() noexcept -> Context& + { + static Context context{}; + return context; + } + + auto Context::draw_list_shared_data() const noexcept -> const DrawListSharedData& + { + if (draw_list_shared_data_current_ == stack_use_default) + { + return draw_list_shared_data_default_; + } + + const auto* p = draw_list_shared_data_stack_[draw_list_shared_data_current_]; + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(p != nullptr); + + return *p; + } + + auto Context::push_draw_list_shared_data(DrawListSharedData& shared_data) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list_shared_data_current_ == stack_use_default or draw_list_shared_data_current_ < draw_list_shared_data_stack_size); + + draw_list_shared_data_current_ += 1; + draw_list_shared_data_stack_[draw_list_shared_data_current_] = std::addressof(shared_data); + } + + auto Context::pop_draw_list_shared_data() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list_shared_data_current_ != stack_use_default, "Unable to popup the default DrawListSharedData!"); + + draw_list_shared_data_stack_[draw_list_shared_data_current_] = nullptr; + if (draw_list_shared_data_current_ == 0) + { + draw_list_shared_data_current_ = stack_use_default; + } + else + { + draw_list_shared_data_current_ -= 1; + } + } + + auto Context::set_default_font(font_type font) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font != nullptr); + + font_default_ = std::move(font); + } + + auto Context::font() const noexcept -> const Font& + { + if (font_current_ == stack_use_default) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font_default_ != nullptr); + + return *font_default_; + } + + const auto* p = font_stack_[font_current_]; + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(p != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(p->get() != nullptr); + + return **p; + } + + auto Context::push_font(font_type& font) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font_current_ == stack_use_default or font_current_ < font_stack_size); + + font_current_ += 1; + font_stack_[font_current_] = std::addressof(font); + } + + auto Context::pop_font() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font_current_ != stack_use_default, "Unable to popup the default Font!"); + + font_stack_[font_current_] = nullptr; + if (font_current_ == 0) + { + font_current_ = stack_use_default; + } + else + { + font_current_ -= 1; + } + } + + auto Context::theme() const noexcept -> const Theme& + { + if (theme_current_ == stack_use_default) + { + return theme_default_; + } + + const auto* p = theme_stack_[theme_current_]; + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(p != nullptr); + + return *p; + } + + auto Context::push_theme(Theme& theme) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(theme_current_== stack_use_default or theme_current_ < theme_stack_size); + + theme_current_ += 1; + theme_stack_[theme_current_] = std::addressof(theme); + } + + auto Context::pop_theme() noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(theme_current_ != stack_use_default, "Unable to popup the default Theme!"); + + theme_stack_[theme_current_] = nullptr; + if (theme_current_ == 0) + { + theme_current_ = stack_use_default; + } + else + { + theme_current_ -= 1; + } + } + + auto Context::tooltip() const noexcept -> std::string_view + { + return tooltip_; + } + + auto Context::mouse() const noexcept -> const Mouse& + { + return mouse_; + } + + auto Context::is_widget_hovered(const id_type id) const noexcept -> bool + { + return widget_id_hovered_ == id; + } + + auto Context::is_widget_activated(const id_type id) const noexcept -> bool + { + return widget_id_activated_ == id; + } + + auto Context::invalidate_widget_hovered( + #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) + const std::string& reason, + const std::source_location& location + #endif + ) noexcept -> void + { + #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) + (void)reason; + (void)location; + #endif + + widget_id_hovered_ = Window::invalid_id; + } + + auto Context::invalidate_widget_activated( + #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) + const std::string& reason, + const std::source_location& location + #endif + ) noexcept -> void + { + #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) + (void)reason; + (void)location; + #endif + + widget_id_activated_ = Window::invalid_id; + } + + auto Context::test_widget_status( + const id_type id, + const rect_type& widget_rect, + const bool repeat + #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) + , + const std::string& reason + #endif + ) noexcept -> widget_status_type + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(window_current_ != nullptr); + + auto& window = *window_current_; + const rect_type rect{widget_rect.point + window.rect().left_top(), widget_rect.extent}; + const auto hovered = window_hovered_ == std::addressof(window) and rect.includes(mouse_.position()); + + widget_status_type widget_status{.hovered = hovered, .pressed = false, .keeping = false}; + if (hovered) + { + widget_id_hovered_ = id; + if (mouse_.clicked()) + { + widget_id_activated_ = id; + } + else if (repeat and widget_id_activated_ != Window::invalid_id) + { + widget_status.pressed = true; + } + } + + if (widget_id_activated_ == id) + { + if (mouse_.down()) + { + widget_status.keeping = true; + } + else + { + if (hovered) + { + widget_status.pressed = true; + } + + invalidate_widget_activated( + #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) + std::format("Release mouse on widget #{}.({})", id, reason) + #endif + ); + } + } + + return widget_status; + } + + auto Context::find_window(const std::string_view name) const noexcept -> std::optional> + { + const auto it = std::ranges::find_if( + windows_, + [name](const auto& window) noexcept -> bool + { + return name == window.name(); + } + ); + + if (it == std::ranges::end(windows_)) + { + return std::nullopt; + } + + return std::make_optional(std::cref(*it)); + } + + auto Context::new_frame() noexcept -> void + { + tooltip_[0] = '\0'; + + // todo + mouse_.tick(1 / 60.f); + + std::ranges::for_each( + windows_ | std::views::reverse, + [this](auto& window) noexcept -> void + { + if (window.hovered(mouse_.position())) + { + window_hovered_ = std::addressof(window); + } + } + ); + + if (mouse_.clicked_) + { + if (window_hovered_ != nullptr) + { + const auto index = find_window(*window_hovered_); + const auto it = windows_.begin() + index; + + auto&& window = std::move(windows_[index]); + windows_.erase(it); + windows_.emplace_back(std::move(window)); + } + } + } + + auto Context::render() noexcept -> void + { + window_draw_lists_.clear(); + std::ranges::transform( + windows_, + std::back_inserter(window_draw_lists_), + [](auto& window) noexcept -> auto + { + return std::ref(window.render()); + } + ); + + if (window_current_ != nullptr and tooltip_[0] != '\0') + { + const auto index = find_window(*window_current_); + + auto& draw_list = window_draw_lists_[index].get(); + const auto& theme = this->theme(); + + // todo + const rect_type tooltip_rect{mouse_.position(), extent_type{100, 100}}; + draw_list.rect_filled(tooltip_rect, theme.color()); + draw_list.text(theme.font_size, mouse_.position(), theme.color(), tooltip_); + } + } +} diff --git a/src/draw/context.hpp b/src/draw/context.hpp new file mode 100644 index 00000000..429e804f --- /dev/null +++ b/src/draw/context.hpp @@ -0,0 +1,185 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace gal::prometheus::draw +{ + class Context final + { + public: + template + using container_type = DrawListDef::container_type; + + using rect_type = DrawListDef::rect_type; + using point_type = DrawListDef::point_type; + using extent_type = DrawListDef::extent_type; + + using font_type = std::shared_ptr; + + constexpr static std::size_t draw_list_shared_data_stack_size = 8; + constexpr static std::size_t font_stack_size = 8; + constexpr static std::size_t theme_stack_size = 8; + + using id_type = Window::id_type; + constexpr static auto invalid_id = Window::invalid_id; + + using windows_type = container_type; + using index_type = windows_type::difference_type; + + private: + using stack_pointer_type = std::uint8_t; + constexpr static auto stack_use_default = std::numeric_limits::max(); + + // ---------------------------------------------------------------------- + // DrawListSharedData + Font + Theme + + DrawListSharedData draw_list_shared_data_default_; + font_type font_default_; + Theme theme_default_; + + DrawListSharedData* draw_list_shared_data_stack_[draw_list_shared_data_stack_size]; + font_type* font_stack_[font_stack_size]; + Theme* theme_stack_[theme_stack_size]; + + stack_pointer_type draw_list_shared_data_current_; + stack_pointer_type font_current_; + stack_pointer_type theme_current_; + stack_pointer_type pad_; + + // ---------------------------------------------------------------------- + // TOOLTIP + + std::string tooltip_; + + // ---------------------------------------------------------------------- + // MOUSE + + Mouse mouse_; + + // ---------------------------------------------------------------------- + // WINDOW + + windows_type windows_; + Window* window_current_; + Window* window_hovered_; + + id_type widget_id_hovered_; + id_type widget_id_activated_; + + container_type> window_draw_lists_; + + [[nodiscard]] auto find_window(const Window& window) const noexcept -> index_type; + + Context() noexcept; + + public: + // --------------------------------------------- + // SINGLETON + + [[nodiscard]] static auto instance() noexcept -> Context&; + + // --------------------------------------------- + // DRAW LIST SHARED DATA + + [[nodiscard]] auto draw_list_shared_data() const noexcept -> const DrawListSharedData&; + + auto push_draw_list_shared_data(DrawListSharedData& shared_data) noexcept -> void; + + auto pop_draw_list_shared_data() noexcept -> void; + + // --------------------------------------------- + // FONT + + auto set_default_font(font_type font) noexcept -> void; + + [[nodiscard]] auto font() const noexcept -> const Font&; + + auto push_font(font_type& font) noexcept -> void; + + auto pop_font() noexcept -> void; + + // --------------------------------------------- + // THEME + + [[nodiscard]] auto theme() const noexcept -> const Theme&; + + auto push_theme(Theme& theme) noexcept -> void; + + auto pop_theme() noexcept -> void; + + // --------------------------------------------- + // TOOLTIP + + [[nodiscard]] auto tooltip() const noexcept -> std::string_view; + + // --------------------------------------------- + // MOUSE + + [[nodiscard]] auto mouse() const noexcept -> const Mouse&; + + // --------------------------------------------- + // WINDOW & WIDGET + + [[nodiscard]] auto is_widget_hovered(id_type id) const noexcept -> bool; + + [[nodiscard]] auto is_widget_activated(id_type id) const noexcept -> bool; + + auto invalidate_widget_hovered( + #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) + const std::string& reason, + const std::source_location& location = std::source_location::current() + #endif + ) noexcept -> void; + + auto invalidate_widget_activated( + #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) + const std::string& reason, + const std::source_location& location = std::source_location::current() + #endif + ) noexcept -> void; + + struct widget_status_type + { + bool hovered; + bool pressed; + bool keeping; + }; + + [[nodiscard]] auto test_widget_status( + id_type id, + const rect_type& widget_rect, + bool repeat + #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) + , + const std::string& reason + #endif + ) noexcept -> widget_status_type; + + [[nodiscard]] auto find_window(std::string_view name) const noexcept -> std::optional>; + + // --------------------------------------------- + // RENDER + + auto new_frame() noexcept -> void; + + auto render() noexcept -> void; + + // --------------------------------------------- + // for test only + + auto test_set_window(Window& window) noexcept -> void + { + window_current_ = &window; + } + }; +} diff --git a/src/draw/def.hpp b/src/draw/def.hpp new file mode 100644 index 00000000..11c615d8 --- /dev/null +++ b/src/draw/def.hpp @@ -0,0 +1,125 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include +#include + +namespace gal::prometheus::draw +{ + class DrawListDef + { + public: + template + using container_type = std::vector; + + // ---------------------------------------------------- + + using rect_type = primitive::basic_rect_2d; + using point_type = rect_type::point_type; + using extent_type = rect_type::extent_type; + + using circle_type = primitive::basic_circle_2d; + using ellipse_type = primitive::basic_ellipse_2d; + + // ---------------------------------------------------- + + using uv_type = primitive::basic_point_2d; + using color_type = primitive::basic_color; + using vertex_type = primitive::basic_vertex; + using index_type = std::uint16_t; + + // ---------------------------------------------------- + + using path_list_type = container_type; + using vertex_list_type = container_type; + using index_list_type = container_type; + + // ---------------------------------------------------- + + using texture_id_type = std::uintptr_t; + using size_type = vertex_list_type::size_type; + + struct command_type + { + rect_type clip_rect; + texture_id_type texture_id; + + // ======================= + + // set by DrawList::index_list.size() + // start offset in `DrawList::index_list` + size_type index_offset; + // set by subsequent `DrawList::draw_xxx` + // number of indices (multiple of 3) to be rendered as triangles + size_type element_count; + }; + + using command_list_type = container_type; + + // ---------------------------------------------------- + + class Accessor + { + // command_type::element_count + std::reference_wrapper element_count_; + // DrawList::vertex_list + std::reference_wrapper vertex_list_; + // DrawList::index_list_; + std::reference_wrapper index_list_; + + public: + constexpr Accessor(command_type& command, vertex_list_type& vertex_list, index_list_type& index_list) noexcept + : element_count_{command.element_count}, + vertex_list_{vertex_list}, + index_list_{index_list} {} + + Accessor(const Accessor& other) = delete; + Accessor(Accessor&& other) noexcept = delete; + auto operator=(const Accessor& other) -> Accessor& = delete; + auto operator=(Accessor&& other) noexcept -> Accessor& = delete; + + ~Accessor() noexcept = default; + + constexpr auto reserve(const size_type vertex_count, const size_type index_count) const noexcept -> void + { + auto& element_count = element_count_.get(); + auto& vertex = vertex_list_.get(); + auto& index = index_list_.get(); + + element_count += index_count; + vertex.reserve(vertex.size() + vertex_count); + index.reserve(index.size() + index_count); + } + + [[nodiscard]] constexpr auto size() const noexcept -> size_type + { + const auto& list = vertex_list_.get(); + + return list.size(); + } + + constexpr auto add_vertex(const point_type& position, const uv_type& uv, const color_type& color) const noexcept -> void + { + auto& list = vertex_list_.get(); + + list.emplace_back(position, uv, color); + } + + constexpr auto add_index(const index_type a, const index_type b, const index_type c) const noexcept -> void + { + auto& list = index_list_.get(); + + list.push_back(a); + list.push_back(b); + list.push_back(c); + } + }; + }; +} diff --git a/src/draw/draw.hpp b/src/draw/draw.hpp new file mode 100644 index 00000000..a30a8629 --- /dev/null +++ b/src/draw/draw.hpp @@ -0,0 +1,14 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include diff --git a/src/draw/draw_list.cpp b/src/draw/draw_list.cpp new file mode 100644 index 00000000..b8290bf1 --- /dev/null +++ b/src/draw/draw_list.cpp @@ -0,0 +1,1857 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. +#include + +#include +#include + +#include +#include +#include + +namespace +{ + using namespace gal::prometheus; + + [[nodiscard]] constexpr auto to_fixed_rect_corner_flag(const draw::DrawFlag flag) noexcept -> draw::DrawFlag + { + using enum draw::DrawFlag; + + if ((flag & ROUND_CORNER_MASK) == NONE) + { + return ROUND_CORNER_ALL | flag; + } + + return flag; + } + + [[nodiscard]] constexpr auto to_fixed_normal(const float x, const float y) noexcept -> std::pair + { + if (const auto d = math::pow(x, 2) + math::pow(y, 2); + d > 1e-6f) + { + // fixme + const auto inv_len = [d] + { + // #if defined(__AVX512F__) + // __m512 d_v = _mm512_set1_ps(d); + // __m512 inv_len_v = _mm512_rcp14_ps(d_v); + // return _mm512_cvtss_f32(inv_len_v); + // #elif defined(__AVX__) + // __m256 d_v = _mm256_set1_ps(d); + // __m256 inv_len_v = _mm256_rcp_ps(d_v); + // return _mm256_cvtss_f32(inv_len_v); + // #elif defined(__SSE4_1__) or defined(__SSE3__) or defined(__SSE__) + // __m128 d_v = _mm_set_ss(d); + // __m128 inv_len_v = _mm_rcp_ss(d_v); + // return _mm_cvtss_f32(inv_len_v); + // #else + return 1.0f / d; + // #endif + }(); + + return {x * inv_len, y * inv_len}; + } + + return {x, y}; + } + + // fixme + constexpr std::size_t bezier_curve_casteljau_max_level = 10; + + using point_type = draw::DrawList::point_type; + + constexpr auto bezier_cubic_calc = [](const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const float tolerance) noexcept -> point_type + { + const auto u = 1.f - tolerance; + + const auto w1 = math::pow(u, 3); + const auto w2 = 3 * math::pow(u, 2) * tolerance; + const auto w3 = 3 * u * math::pow(tolerance, 2); + const auto w4 = math::pow(tolerance, 3); + + return + { + p1.x * w1 + p2.x * w2 + p3.x * w3 + p4.x * w4, + p1.y * w1 + p2.y * w2 + p3.y * w3 + p4.y * w4 + }; + }; + + constexpr auto bezier_quadratic_calc = [](const point_type& p1, const point_type& p2, const point_type& p3, const float tolerance) noexcept -> point_type + { + const auto u = 1.f - tolerance; + + const auto w1 = math::pow(u, 2); + const auto w2 = 2 * u * tolerance; + const auto w3 = math::pow(tolerance, 2); + + return + { + p1.x * w1 + p2.x * w2 + p3.x * w3, + p1.y * w1 + p2.y * w2 + p3.y * w3 + }; + }; +} + +namespace gal::prometheus::draw +{ + auto DrawList::make_accessor() noexcept -> DrawListDef::Accessor + { + auto& [command_list, vertex_list, index_list] = private_data_; + + return + { + command_list.back(), + vertex_list, + index_list + }; + } + + auto DrawList::push_command() noexcept -> void + { + // fixme: If the window boundary is smaller than the rect boundary, the rect will no longer be valid. + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not this_command_clip_rect_.empty() and this_command_clip_rect_.valid()); + + auto& [command_list, _, index_list] = private_data_; + + command_list.emplace_back( + command_type + { + .clip_rect = this_command_clip_rect_, + .texture_id = this_command_texture_id_, + .index_offset = index_list.size(), + // set by subsequent draw_xxx + .element_count = 0 + } + ); + } + + auto DrawList::on_element_changed(const ChangedElement element) noexcept -> void + { + auto& [command_list, vertex_list, index_list] = private_data_; + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list.empty()); + + const auto command_count = command_list.size(); + const auto& [current_clip_rect, current_texture_id, current_index_offset, current_element_count] = command_list.back(); + + if (current_element_count != 0) + { + if (element == ChangedElement::CLIP_RECT) + { + if (current_clip_rect != this_command_clip_rect_) + { + push_command(); + + return; + } + } + else if (element == ChangedElement::TEXTURE_ID) + { + if (current_texture_id != this_command_texture_id_) + { + push_command(); + + return; + } + } + else + { + GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); + } + } + + // try to merge with previous command if it matches, else use current command + if (command_count > 1) + { + if ( + const auto& [previous_clip_rect, previous_texture, previous_index_offset, previous_element_count] = command_list[command_count - 2]; + current_element_count == 0 and + ( + this_command_clip_rect_ == previous_clip_rect and + this_command_texture_id_ == previous_texture + ) and + // sequential + current_index_offset == previous_index_offset + previous_element_count + ) + { + command_list.pop_back(); + return; + } + } + + if (element == ChangedElement::CLIP_RECT) + { + command_list.back().clip_rect = this_command_clip_rect_; + } + else if (element == ChangedElement::TEXTURE_ID) + { + command_list.back().texture_id = this_command_texture_id_; + } + else + { + GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); + } + } + + auto DrawList::draw_polygon_line(const color_type& color, const DrawFlag draw_flag, const float thickness) noexcept -> void + { + const auto& font = Context::instance().font(); + + const auto accessor = make_accessor(); + + const auto path_point_count = path_list_.size(); + const auto& path_point = path_list_; + + if (path_point_count < 2 or color.alpha == 0) + { + return; + } + + const auto is_closed = (draw_flag & DrawFlag::CLOSED) != DrawFlag::NONE; + const auto segments_count = is_closed ? path_point_count : path_point_count - 1; + + const auto vertex_count = segments_count * 4; + const auto index_count = segments_count * 6; + accessor.reserve(vertex_count, index_count); + + for (std::decay_t i = 0; i < segments_count; ++i) + { + const auto n = (i + 1) % path_point_count; + + const auto& p1 = path_point[i]; + const auto& p2 = path_point[n]; + + auto [normalized_x, normalized_y] = math::normalize(p2.x - p1.x, p2.y - p1.y); + normalized_x *= (thickness * .5f); + normalized_y *= (thickness * .5f); + + const auto current_vertex_index = static_cast(accessor.size()); + const auto& opaque_uv = font.white_pixel_uv(); + + accessor.add_vertex(p1 + point_type{normalized_y, -normalized_x}, opaque_uv, color); + accessor.add_vertex(p2 + point_type{normalized_y, -normalized_x}, opaque_uv, color); + accessor.add_vertex(p2 + point_type{-normalized_y, normalized_x}, opaque_uv, color); + accessor.add_vertex(p1 + point_type{-normalized_y, normalized_x}, opaque_uv, color); + + accessor.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + accessor.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + } + } + + auto DrawList::draw_polygon_line_aa(const color_type& color, const DrawFlag draw_flag, float thickness) noexcept -> void + { + const auto& font = Context::instance().font(); + + const auto accessor = make_accessor(); + + const auto path_point_count = path_list_.size(); + const auto& path_point = path_list_; + + if (path_point_count < 2 or color.alpha == 0) + { + return; + } + + const auto& opaque_uv = font.white_pixel_uv(); + const auto transparent_color = color.transparent(); + + const auto is_closed = (draw_flag & DrawFlag::CLOSED) != DrawFlag::NONE; + const auto segments_count = is_closed ? path_point_count : path_point_count - 1; + const auto is_thick_line = thickness > 1.f; + + thickness = std::ranges::max(thickness, 1.f); + const auto thickness_integer = static_cast(thickness); + const auto thickness_fractional = thickness - static_cast(thickness_integer); + + const auto is_use_texture = + ( + (draw_list_flag_ & DrawListFlag::ANTI_ALIASED_LINE_USE_TEXTURE) == DrawListFlag::ANTI_ALIASED_LINE_USE_TEXTURE and + (thickness_integer < font.baked_line_max_width()) and + (thickness_fractional <= .00001f)); + + const auto vertex_cont = is_use_texture ? (path_point_count * 2) : (is_thick_line ? path_point_count * 4 : path_point_count * 3); + const auto index_count = is_use_texture ? (segments_count * 6) : (is_thick_line ? segments_count * 18 : segments_count * 12); + accessor.reserve(vertex_cont, index_count); + + // The first items are normals at each line point, then after that there are either 2 or 4 temp points for each line point + container_type temp_buffer{}; + temp_buffer.resize(path_point_count * ((is_use_texture or not is_thick_line) ? 3 : 5)); + auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; + auto temp_buffer_points = std::span{temp_buffer.begin() + static_cast(path_point_count), temp_buffer.end()}; + + // Calculate normals (tangents) for each line segment + for (std::decay_t i = 0; i < segments_count; ++i) + { + const auto n = (i + 1) % path_point_count; + const auto d = path_point[n] - path_point[i]; + + const auto [normalized_x, normalized_y] = math::normalize(d.x, d.y); + temp_buffer_normals[i].x = normalized_y; + temp_buffer_normals[i].y = -normalized_x; + } + + if (not is_closed) + { + temp_buffer_normals[temp_buffer_normals.size() - 1] = temp_buffer_normals[temp_buffer_normals.size() - 2]; + } + + // If we are drawing a one-pixel-wide line without a texture, or a textured line of any width, we only need 2 or 3 vertices per point + if (is_use_texture or not is_thick_line) + { + // [PATH 1] Texture-based lines (thick or non-thick) + + // The width of the geometry we need to draw - this is essentially pixels for the line itself, plus "one pixel" for AA + const auto half_draw_size = is_use_texture ? ((thickness * .5f) + 1.f) : 1.f; + + // If line is not closed, the first and last points need to be generated differently as there are no normals to blend + if (not is_closed) + { + temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * half_draw_size; + temp_buffer_points[1] = path_point[0] - temp_buffer_normals[0] * half_draw_size; + temp_buffer_points[(path_point_count - 1) * 2 + 0] = path_point[path_point_count - 1] + temp_buffer_normals[path_point_count - 1] * half_draw_size; + temp_buffer_points[(path_point_count - 1) * 2 + 1] = path_point[path_point_count - 1] - temp_buffer_normals[path_point_count - 1] * half_draw_size; + } + + const auto current_vertex_index = static_cast(accessor.size()); + + // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges + // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) + auto vertex_index_for_start = current_vertex_index; + for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) + { + const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; + const auto vertex_index_for_end = static_cast( + // closed + (first_point_of_segment + 1) == path_point_count + ? current_vertex_index + : (vertex_index_for_start + (is_use_texture ? 2 : 3)) + ); + + // Average normals + const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; + // dm_x, dm_y are offset to the outer edge of the AA area + auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); + dm_x *= half_draw_size; + dm_y *= half_draw_size; + + // Add temporary vertexes for the outer edges + temp_buffer_points[second_point_of_segment * 2 + 0] = path_point[second_point_of_segment] + point_type{dm_x, dm_y}; + temp_buffer_points[second_point_of_segment * 2 + 1] = path_point[second_point_of_segment] - point_type{dm_x, dm_y}; + + if (is_use_texture) + { + // Add indices for two triangles + + // right + accessor.add_index(vertex_index_for_end + 0, vertex_index_for_start + 0, vertex_index_for_start + 1); + // left + accessor.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_end + 0); + } + else + { + // Add indexes for four triangles + + // right 1 + accessor.add_index(vertex_index_for_end + 0, vertex_index_for_start + 0, vertex_index_for_start + 2); + // right 2 + accessor.add_index(vertex_index_for_start + 2, vertex_index_for_end + 2, vertex_index_for_end + 0); + // left 1 + accessor.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_start + 0); + // left 2 + accessor.add_index(vertex_index_for_start + 0, vertex_index_for_end + 0, vertex_index_for_end + 1); + } + + vertex_index_for_start = vertex_index_for_end; + } + + // Add vertexes for each point on the line + if (is_use_texture) + { + GAL_PROMETHEUS_ERROR_ASSUME(not font.baked_line_uv().empty(), "draw::FontAtlasFlag::NO_BAKED_LINE"); + + const auto& uv = font.baked_line_uv()[thickness_integer]; + + const auto uv0 = uv.left_top(); + const auto uv1 = uv.right_bottom(); + for (std::decay_t i = 0; i < path_point_count; ++i) + { + // left-side outer edge + accessor.add_vertex(temp_buffer_points[i * 2 + 0], uv0, color); + // right-side outer edge + accessor.add_vertex(temp_buffer_points[i * 2 + 1], uv1, color); + } + } + else + { + // If we're not using a texture, we need the center vertex as well + for (std::decay_t i = 0; i < path_point_count; ++i) + { + // center of line + accessor.add_vertex(path_point[i], opaque_uv, color); + // left-side outer edge + accessor.add_vertex(temp_buffer_points[i * 2 + 0], opaque_uv, transparent_color); + // right-side outer edge + accessor.add_vertex(temp_buffer_points[i * 2 + 1], opaque_uv, transparent_color); + } + } + } + else + { + // [PATH 2] Non-texture-based lines (non-thick) + + // we need to draw the solid line core and thus require four vertices per point + const auto half_inner_thickness = (thickness - 1.f) * .5f; + + // If line is not closed, the first and last points need to be generated differently as there are no normals to blend + if (not is_closed) + { + const auto point_last = path_point_count - 1; + temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 1.f); + temp_buffer_points[1] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 0.f); + temp_buffer_points[2] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 0.f); + temp_buffer_points[3] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 1.f); + temp_buffer_points[point_last * 4 + 0] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); + temp_buffer_points[point_last * 4 + 1] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); + temp_buffer_points[point_last * 4 + 2] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); + temp_buffer_points[point_last * 4 + 3] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); + } + + const auto current_vertex_index = static_cast(accessor.size()); + + // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges + // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) + auto vertex_index_for_start = current_vertex_index; + for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) + { + const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; + const auto vertex_index_for_end = static_cast( + (first_point_of_segment + 1) == path_point_count + ? current_vertex_index + : (vertex_index_for_start + 4) + ); + + // Average normals + const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; + const auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); + const auto dm_out_x = dm_x * (half_inner_thickness + 1.f); + const auto dm_out_y = dm_y * (half_inner_thickness + 1.f); + const auto dm_in_x = dm_x * (half_inner_thickness + 0.f); + const auto dm_in_y = dm_y * (half_inner_thickness + 0.f); + + // Add temporary vertices + temp_buffer_points[second_point_of_segment * 4 + 0] = path_point[second_point_of_segment] + point_type{dm_out_x, dm_out_y}; + temp_buffer_points[second_point_of_segment * 4 + 1] = path_point[second_point_of_segment] + point_type{dm_in_x, dm_in_y}; + temp_buffer_points[second_point_of_segment * 4 + 2] = path_point[second_point_of_segment] - point_type{dm_in_x, dm_in_y}; + temp_buffer_points[second_point_of_segment * 4 + 3] = path_point[second_point_of_segment] - point_type{dm_out_x, dm_out_y}; + + // Add indexes + accessor.add_index(vertex_index_for_end + 1, vertex_index_for_end + 1, vertex_index_for_start + 2); + accessor.add_index(vertex_index_for_start + 2, vertex_index_for_end + 2, vertex_index_for_end + 1); + accessor.add_index(vertex_index_for_end + 1, vertex_index_for_start + 1, vertex_index_for_start + 0); + accessor.add_index(vertex_index_for_start + 0, vertex_index_for_end + 0, vertex_index_for_end + 1); + accessor.add_index(vertex_index_for_end + 2, vertex_index_for_start + 2, vertex_index_for_start + 3); + accessor.add_index(vertex_index_for_start + 3, vertex_index_for_end + 3, vertex_index_for_end + 2); + + vertex_index_for_start = vertex_index_for_end; + } + + // Add vertices + for (std::decay_t i = 0; i < path_point_count; ++i) + { + accessor.add_vertex(temp_buffer_points[i * 4 + 0], opaque_uv, transparent_color); + accessor.add_vertex(temp_buffer_points[i * 4 + 1], opaque_uv, color); + accessor.add_vertex(temp_buffer_points[i * 4 + 2], opaque_uv, color); + accessor.add_vertex(temp_buffer_points[i * 4 + 2], opaque_uv, transparent_color); + } + } + } + + auto DrawList::draw_convex_polygon_line_filled(const color_type& color) noexcept -> void + { + const auto& font = Context::instance().font(); + + const auto accessor = make_accessor(); + + const auto path_point_count = path_list_.size(); + const auto& path_point = path_list_; + + if (path_point_count < 3 or color.alpha == 0) + { + return; + } + + const auto vertex_count = path_point_count; + const auto index_count = (path_point_count - 2) * 3; + accessor.reserve(vertex_count, index_count); + + const auto current_vertex_index = static_cast(accessor.size()); + const auto& opaque_uv = font.white_pixel_uv(); + + std::ranges::for_each( + path_point, + [&](const point_type& point) noexcept -> void + { + accessor.add_vertex(point, opaque_uv, color); + } + ); + for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i - 1 >= current_vertex_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i >= current_vertex_index); + + accessor.add_index(current_vertex_index + 0, current_vertex_index + i - 1, current_vertex_index + i); + } + } + + auto DrawList::draw_convex_polygon_line_filled_aa(const color_type& color) noexcept -> void + { + const auto& font = Context::instance().font(); + + const auto accessor = make_accessor(); + + const auto path_point_count = path_list_.size(); + const auto& path_point = path_list_; + + if (path_point_count < 3 or color.alpha == 0) + { + return; + } + + const auto& opaque_uv = font.white_pixel_uv(); + const auto transparent_color = color.transparent(); + + const auto vertex_count = path_point_count * 2; + const auto index_count = (path_point_count - 2) * 3 + path_point_count * 6; + accessor.reserve(vertex_count, index_count); + + const auto current_vertex_inner_index = static_cast(accessor.size()); + const auto current_vertex_outer_index = static_cast(accessor.size() + 1); + + // Add indexes for fill + for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast((i - 1) << 1) >= current_vertex_inner_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(i << 1) >= current_vertex_inner_index); + + accessor.add_index( + current_vertex_inner_index + 0, + current_vertex_inner_index + static_cast((i - 1) << 1), + current_vertex_inner_index + static_cast(i << 1) + ); + } + + container_type temp_buffer{}; + temp_buffer.resize(path_point_count); + auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; + + for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) + { + const auto d = path_point[n] - path_point[i]; + + const auto [normalized_x, normalized_y] = math::normalize(d.x, d.y); + temp_buffer_normals[i].x = normalized_y; + temp_buffer_normals[i].y = -normalized_x; + } + for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) + { + // Average normals + const auto d = (temp_buffer_normals[n] + temp_buffer_normals[i]) * .5f; + auto [dm_x, dm_y] = to_fixed_normal(d.x, d.y); + dm_x *= .5f; + dm_y *= .5f; + + // inner + accessor.add_vertex(path_point[n] - point_type{dm_x, dm_y}, opaque_uv, color); + // outer + accessor.add_vertex(path_point[n] + point_type{dm_x, dm_y}, opaque_uv, transparent_color); + + // Add indexes for fringes + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(i << 1) >= current_vertex_inner_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(i << 1) >= current_vertex_outer_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(i << 1) >= current_vertex_outer_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + static_cast(n << 1) >= current_vertex_outer_index); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + static_cast(n << 1) >= current_vertex_inner_index); + + accessor.add_index( + current_vertex_inner_index + static_cast(n << 1), + current_vertex_inner_index + static_cast(i << 1), + current_vertex_outer_index + static_cast(i << 1) + ); + accessor.add_index( + current_vertex_outer_index + static_cast(i << 1), + current_vertex_outer_index + static_cast(n << 1), + current_vertex_inner_index + static_cast(n << 1) + ); + } + } + + auto DrawList::draw_rect_filled( + const rect_type& rect, + const color_type& color_left_top, + const color_type& color_right_top, + const color_type& color_left_bottom, + const color_type& color_right_bottom + ) noexcept -> void + { + const auto& font = Context::instance().font(); + + const auto accessor = make_accessor(); + + // two triangle without path + constexpr auto vertex_count = 4; + constexpr auto index_count = 6; + accessor.reserve(vertex_count, index_count); + + const auto& opaque_uv = font.white_pixel_uv(); + + const auto current_vertex_index = static_cast(accessor.size()); + + accessor.add_vertex(rect.left_top(), opaque_uv, color_left_top); + accessor.add_vertex(rect.right_top(), opaque_uv, color_right_top); + accessor.add_vertex(rect.right_bottom(), opaque_uv, color_right_bottom); + accessor.add_vertex(rect.left_bottom(), opaque_uv, color_left_bottom); + + accessor.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + accessor.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + } + + auto DrawList::draw_text( + const Font& font, + const float font_size, + const point_type& p, + const color_type& color, + const std::string_view utf8_text, + const float wrap_width + ) noexcept -> void + { + const auto new_texture = this_command_texture_id_ != font.texture_id(); + + if (new_texture) + { + push_texture_id(font.texture_id()); + } + + font.text_draw(utf8_text, font_size, wrap_width, p, color, make_accessor()); + + if (new_texture) + { + pop_texture_id(); + } + } + + auto DrawList::draw_image( + const texture_id_type texture_id, + const point_type& display_p1, + const point_type& display_p2, + const point_type& display_p3, + const point_type& display_p4, + const uv_type& uv_p1, + const uv_type& uv_p2, + const uv_type& uv_p3, + const uv_type& uv_p4, + const color_type& color + ) noexcept -> void + { + const auto new_texture = this_command_texture_id_ != texture_id; + + if (new_texture) + { + push_texture_id(texture_id); + } + + const auto accessor = make_accessor(); + + // two triangle without path + constexpr auto vertex_count = 4; + constexpr auto index_count = 6; + accessor.reserve(vertex_count, index_count); + + const auto current_vertex_index = static_cast(accessor.size()); + + accessor.add_vertex(display_p1, uv_p1, color); + accessor.add_vertex(display_p2, uv_p2, color); + accessor.add_vertex(display_p3, uv_p3, color); + accessor.add_vertex(display_p4, uv_p4, color); + + accessor.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + accessor.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + + if (new_texture) + { + pop_texture_id(); + } + } + + auto DrawList::draw_image_rounded( + const texture_id_type texture_id, + const rect_type& display_rect, + const rect_type& uv_rect, + const color_type& color, + float rounding, + DrawFlag flag + ) noexcept -> void + { + // @see `path_rect` + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(display_rect.valid() and not display_rect.empty()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(uv_rect.valid() and not uv_rect.empty()); + + if (rounding >= .5f) + { + flag = to_fixed_rect_corner_flag(flag); + + const auto v = + (flag & DrawFlag::ROUND_CORNER_TOP) == DrawFlag::ROUND_CORNER_TOP or + (flag & DrawFlag::ROUND_CORNER_BOTTOM) == DrawFlag::ROUND_CORNER_BOTTOM; + const auto h = + (flag & DrawFlag::ROUND_CORNER_LEFT) == DrawFlag::ROUND_CORNER_LEFT or + (flag & DrawFlag::ROUND_CORNER_RIGHT) == DrawFlag::ROUND_CORNER_RIGHT; + + rounding = std::ranges::min(rounding, display_rect.width() * (v ? .5f : 1.f) - 1.f); + rounding = std::ranges::min(rounding, display_rect.height() * (h ? .5f : 1.f) - 1.f); + } + + if (rounding < .5f or (DrawFlag::ROUND_CORNER_MASK & flag) == DrawFlag::ROUND_CORNER_NONE) + { + draw_image( + texture_id, + display_rect.left_top(), + display_rect.right_top(), + display_rect.right_bottom(), + display_rect.left_bottom(), + uv_rect.left_top(), + uv_rect.right_top(), + uv_rect.right_bottom(), + uv_rect.left_bottom(), + color + ); + } + else + { + const auto new_texture = this_command_texture_id_ != texture_id; + + if (new_texture) + { + push_texture_id(texture_id); + } + + const auto rounding_left_top = (flag & DrawFlag::ROUND_CORNER_LEFT_TOP) != DrawFlag::NONE ? rounding : 0; + const auto rounding_right_top = (flag & DrawFlag::ROUND_CORNER_RIGHT_TOP) != DrawFlag::NONE ? rounding : 0; + const auto rounding_left_bottom = (flag & DrawFlag::ROUND_CORNER_LEFT_BOTTOM) != DrawFlag::NONE ? rounding : 0; + const auto rounding_right_bottom = (flag & DrawFlag::ROUND_CORNER_RIGHT_BOTTOM) != DrawFlag::NONE ? rounding : 0; + + path_arc_fast({display_rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, DrawArcFlag::Q2_CLOCK_WISH); + path_arc_fast({display_rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, DrawArcFlag::Q1_CLOCK_WISH); + path_arc_fast({display_rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, DrawArcFlag::Q4_CLOCK_WISH); + path_arc_fast({display_rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, DrawArcFlag::Q3_CLOCK_WISH); + + auto& vertex_list = private_data_.vertex_list; + + const auto before_vertex_count = vertex_list.size(); + // draw + path_stroke(color); + const auto after_vertex_count = vertex_list.size(); + + // set uv manually + + const auto display_size = display_rect.size(); + const auto uv_size = uv_rect.size(); + const auto scale = uv_size / display_size; + + auto it = vertex_list.begin() + static_cast(before_vertex_count); + const auto end = vertex_list.begin() + static_cast(after_vertex_count); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it < end); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(end == vertex_list.end()); + + // note: linear uv + const auto uv_min = uv_rect.left_top(); + // const auto uv_max = uv_rect.right_bottom(); + while (it != end) + { + const auto v = uv_min + (it->position - display_rect.left_top()) * scale; + + it->uv = + { + // std::ranges::clamp(v.x, uv_min.x, uv_max.x), + v.x, + // std::ranges::clamp(v.y, uv_min.y, uv_max.y) + v.y + }; + it += 1; + } + + if (new_texture) + { + pop_texture_id(); + } + } + } + + auto DrawList::path_stroke(const color_type& color, const DrawFlag flag, const float thickness) noexcept -> void + { + if ((draw_list_flag_ & DrawListFlag::ANTI_ALIASED_LINE) != DrawListFlag::NONE) + { + draw_polygon_line_aa(color, flag, thickness); + } + else + { + draw_polygon_line(color, flag, thickness); + } + + path_clear(); + } + + auto DrawList::path_stroke(const color_type& color) noexcept -> void + { + if ((draw_list_flag_ & DrawListFlag::ANTI_ALIASED_FILL) != DrawListFlag::NONE) + { + draw_convex_polygon_line_filled_aa(color); + } + else + { + draw_convex_polygon_line_filled(color); + } + + path_clear(); + } + + auto DrawList::path_arc_fast(const circle_type& circle, const int from, const int to) noexcept -> void + { + const auto& draw_list_shared_data = Context::instance().draw_list_shared_data(); + + const auto& [center, radius] = circle; + + if (radius < .5f) + { + path_pin(center); + return; + } + + // Calculate arc auto segment step size + auto step = DrawListSharedData::vertex_sample_points_count / draw_list_shared_data.get_circle_auto_segment_count(radius); + // Make sure we never do steps larger than one quarter of the circle + step = std::clamp(step, static_cast(1), DrawListSharedData::vertex_sample_points_count / 4); + + const auto sample_range = math::abs(to - from); + const auto next_step = step; + + auto extra_max_sample = false; + if (step > 1) + { + const auto overstep = sample_range % step; + if (overstep > 0) + { + extra_max_sample = true; + + // When we have overstepped to avoid awkwardly looking one long line and one tiny one at the end, + // distribute first step range evenly between them by reducing first step size. + step -= (step - overstep) / 2; + } + + path_reserve_extra(sample_range / step + 1 + (overstep > 0)); + } + else + { + path_reserve_extra(sample_range + 1); + } + + auto sample_index = from; + if (sample_index < 0 or std::cmp_greater_equal(sample_index, DrawListSharedData::vertex_sample_points_count)) + { + sample_index = sample_index % static_cast(DrawListSharedData::vertex_sample_points_count); + if (sample_index < 0) + { + sample_index += static_cast(DrawListSharedData::vertex_sample_points_count); + } + } + + if (to >= from) + { + for (int i = from; i <= to; i += static_cast(step), sample_index += static_cast(step), step = next_step) + { + // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more + if (std::cmp_greater_equal(sample_index, DrawListSharedData::vertex_sample_points_count)) + { + sample_index -= static_cast(DrawListSharedData::vertex_sample_points_count); + } + + const auto& sample_point = draw_list_shared_data.get_vertex_sample_point(sample_index); + + path_pin({center + sample_point * radius}); + } + } + else + { + for (int i = from; i >= to; i -= static_cast(step), sample_index -= static_cast(step), step = next_step) + { + // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more + if (sample_index < 0) + { + sample_index += static_cast(DrawListSharedData::vertex_sample_points_count); + } + + const auto& sample_point = draw_list_shared_data.get_vertex_sample_point(sample_index); + + path_pin({center + sample_point * radius}); + } + } + + if (extra_max_sample) + { + auto normalized_max_sample_index = to % static_cast(DrawListSharedData::vertex_sample_points_count); + if (normalized_max_sample_index < 0) + { + normalized_max_sample_index += DrawListSharedData::vertex_sample_points_count; + } + + const auto& sample_point = draw_list_shared_data.get_vertex_sample_point(normalized_max_sample_index); + + path_pin({center + sample_point * radius}); + } + } + + auto DrawList::path_arc_fast(const circle_type& circle, const DrawArcFlag flag) noexcept -> void + { + const auto [from, to] = range_of_arc_quadrant(flag); + + return path_arc_fast(circle, from, to); + } + + auto DrawList::path_arc_n(const circle_type& circle, const float from, const float to, const std::uint32_t segments) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(to > from); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(from >= 0); + + const auto& [center, radius] = circle; + + if (radius < .5f) + { + path_pin(center); + return; + } + + path_reserve_extra(segments); + for (std::uint32_t i = 0; i < segments; ++i) + { + const auto a = from + static_cast(i) / static_cast(segments) * (to - from); + path_pin({center + point_type{math::cos(a), math::sin(a)} * radius}); + } + } + + auto DrawList::path_arc(const circle_type& circle, const float from, const float to) noexcept -> void + { + const auto& draw_list_shared_data = Context::instance().draw_list_shared_data(); + + const auto& [center, radius] = circle; + + if (radius < .5f) + { + path_pin(center); + return; + } + + // Automatic segment count + if (radius <= draw_list_shared_data.get_arc_fast_radius_cutoff()) + { + const auto is_reversed = to < from; + + // We are going to use precomputed values for mid-samples. + // Determine first and last sample in lookup table that belong to the arc + const auto sample_from_f = DrawListSharedData::vertex_sample_points_count * from / (std::numbers::pi_v * 2); + const auto sample_to_f = DrawListSharedData::vertex_sample_points_count * to / (std::numbers::pi_v * 2); + + const auto sample_from = is_reversed ? static_cast(math::floor(sample_from_f)) : static_cast(math::ceil(sample_from_f)); + const auto sample_to = is_reversed ? static_cast(math::ceil(sample_to_f)) : static_cast(math::floor(sample_to_f)); + const auto sample_mid = is_reversed ? static_cast(std::ranges::max(sample_from - sample_to, 0)) : static_cast(std::ranges::max(sample_to - sample_from, 0)); + + const auto segment_from_angle = static_cast(sample_from) * std::numbers::pi_v * 2 / DrawListSharedData::vertex_sample_points_count; + const auto segment_to_angle = static_cast(sample_to) * std::numbers::pi_v * 2 / DrawListSharedData::vertex_sample_points_count; + + const auto emit_start = math::abs(segment_from_angle - from) >= 1e-5f; + const auto emit_end = math::abs(to - segment_to_angle) >= 1e-5f; + + if (emit_start) + { + // The quadrant must be the same, otherwise it is not continuous with the path drawn by `path_arc_fast`. + path_pin({center + point_type{math::cos(from), -math::sin(from)} * radius}); + } + if (sample_mid > 0) + { + path_arc_fast(circle, sample_from, sample_to); + } + if (emit_end) + { + // The quadrant must be the same, otherwise it is not continuous with the path drawn by `path_arc_fast`. + path_pin({center + point_type{math::cos(to), -math::sin(to)} * radius}); + } + } + else + { + const auto arc_length = to - from; + const auto circle_segment_count = draw_list_shared_data.get_circle_auto_segment_count(radius); + const auto arc_segment_count = std::ranges::max( + static_cast(math::ceil(static_cast(circle_segment_count) * arc_length / (std::numbers::pi_v * 2))), + static_cast(std::numbers::pi_v * 2 / arc_length) + ); + path_arc_n(circle, from, to, arc_segment_count); + } + } + + auto DrawList::path_arc_elliptical_n(const ellipse_type& ellipse, const float from, const float to, const std::uint32_t segments) noexcept -> void + { + const auto& [center, radius, rotation] = ellipse; + const auto cos_theta = math::cos(rotation); + const auto sin_theta = math::sin(rotation); + + path_reserve_extra(segments); + for (std::uint32_t i = 0; i < segments; ++i) + { + const auto a = from + static_cast(i) / static_cast(segments) * (to - from); + const auto offset = point_type{math::cos(a), math::sin(a)} * radius; + const auto prime_x = offset.x * cos_theta - offset.y * sin_theta; + const auto prime_y = offset.x * sin_theta + offset.y * cos_theta; + path_pin({center + point_type{prime_x, prime_y}}); + } + } + + auto DrawList::path_quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> void + { + path_pin(p1); + path_pin(p2); + path_pin(p3); + path_pin(p4); + } + + auto DrawList::path_rect(const rect_type& rect, float rounding, DrawFlag flag) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(rect.valid() and not rect.empty()); + + if (rounding >= .5f) + { + flag = to_fixed_rect_corner_flag(flag); + + const auto v = + (flag & DrawFlag::ROUND_CORNER_TOP) == DrawFlag::ROUND_CORNER_TOP or + (flag & DrawFlag::ROUND_CORNER_BOTTOM) == DrawFlag::ROUND_CORNER_BOTTOM; + const auto h = + (flag & DrawFlag::ROUND_CORNER_LEFT) == DrawFlag::ROUND_CORNER_LEFT or + (flag & DrawFlag::ROUND_CORNER_RIGHT) == DrawFlag::ROUND_CORNER_RIGHT; + + rounding = std::ranges::min(rounding, rect.width() * (v ? .5f : 1.f) - 1.f); + rounding = std::ranges::min(rounding, rect.height() * (h ? .5f : 1.f) - 1.f); + } + + if (rounding < .5f or (DrawFlag::ROUND_CORNER_MASK & flag) == DrawFlag::ROUND_CORNER_NONE) + { + path_quadrilateral(rect.left_top(), rect.right_top(), rect.right_bottom(), rect.left_bottom()); + } + else + { + const auto rounding_left_top = (flag & DrawFlag::ROUND_CORNER_LEFT_TOP) != DrawFlag::NONE ? rounding : 0; + const auto rounding_right_top = (flag & DrawFlag::ROUND_CORNER_RIGHT_TOP) != DrawFlag::NONE ? rounding : 0; + const auto rounding_left_bottom = (flag & DrawFlag::ROUND_CORNER_LEFT_BOTTOM) != DrawFlag::NONE ? rounding : 0; + const auto rounding_right_bottom = (flag & DrawFlag::ROUND_CORNER_RIGHT_BOTTOM) != DrawFlag::NONE ? rounding : 0; + + path_arc_fast({rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, DrawArcFlag::Q2_CLOCK_WISH); + path_arc_fast({rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, DrawArcFlag::Q1_CLOCK_WISH); + path_arc_fast({rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, DrawArcFlag::Q4_CLOCK_WISH); + path_arc_fast({rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, DrawArcFlag::Q3_CLOCK_WISH); + } + } + + auto DrawList::path_bezier_cubic_curve_casteljau( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const float tessellation_tolerance, + const std::size_t level + ) noexcept -> void + { + const auto dx = p4.x - p1.x; + const auto dy = p4.y - p1.y; + const auto d2 = math::abs((p2.x - p4.x) * dy - (p2.y - p4.y) * dx); + const auto d3 = math::abs((p3.x - p4.x) * dy - (p3.y - p4.y) * dx); + + if (math::pow(d2 + d3, 2) < tessellation_tolerance * (math::pow(dx, 2) + math::pow(dy, 2))) + { + path_pin(p4); + } + else if (level < bezier_curve_casteljau_max_level) + { + const auto p_12 = (p1 + p2) * .5f; + const auto p_23 = (p2 + p3) * .5f; + const auto p_34 = (p3 + p4) * .5f; + const auto p_123 = (p_12 + p_23) * .5f; + const auto p_234 = (p_23 + p_34) * .5f; + const auto p_1234 = (p_123 + p_234) * .5f; + + path_bezier_cubic_curve_casteljau(p1, p_12, p_123, p_1234, tessellation_tolerance, level + 1); + path_bezier_cubic_curve_casteljau(p_1234, p_234, p_34, p4, tessellation_tolerance, level + 1); + } + } + + auto DrawList::path_bezier_quadratic_curve_casteljau(const point_type& p1, const point_type& p2, const point_type& p3, const float tessellation_tolerance, const std::size_t level) noexcept -> void + { + const auto dx = p3.x - p1.x; + const auto dy = p3.y - p1.y; + const auto det = (p2.x - p3.x) * dy - (p2.y - p3.y) * dx; + + if (math::pow(det, 2) * 4.f < tessellation_tolerance * (math::pow(dx, 2) + math::pow(dy, 2))) + { + path_pin(p3); + } + else if (level < bezier_curve_casteljau_max_level) + { + const auto p_12 = (p1 + p2) * .5f; + const auto p_23 = (p2 + p3) * .5f; + const auto p_123 = (p_12 + p_23) * .5f; + + path_bezier_quadratic_curve_casteljau(p1, p_12, p_123, tessellation_tolerance, level + 1); + path_bezier_quadratic_curve_casteljau(p_123, p_23, p3, tessellation_tolerance, level + 1); + } + } + + auto DrawList::path_bezier_curve(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const std::uint32_t segments) noexcept -> void + { + const auto& draw_list_shared_data = Context::instance().draw_list_shared_data(); + + path_pin(p1); + if (segments == 0) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list_shared_data.get_curve_tessellation_tolerance() > 0); + + path_reserve_extra(bezier_curve_casteljau_max_level * 2); + // auto-tessellated + path_bezier_cubic_curve_casteljau(p1, p2, p3, p4, draw_list_shared_data.get_curve_tessellation_tolerance(), 0); + } + else + { + path_reserve_extra(segments); + const auto step = 1.f / static_cast(segments); + for (std::uint32_t i = 1; i <= segments; ++i) + { + path_pin(bezier_cubic_calc(p1, p2, p3, p4, step * static_cast(i))); + } + } + } + + auto DrawList::path_bezier_quadratic_curve(const point_type& p1, const point_type& p2, const point_type& p3, const std::uint32_t segments) noexcept -> void + { + const auto& draw_list_shared_data = Context::instance().draw_list_shared_data(); + + path_pin(p1); + if (segments == 0) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(draw_list_shared_data.get_curve_tessellation_tolerance() > 0); + + path_reserve_extra(bezier_curve_casteljau_max_level * 2); + // auto-tessellated + path_bezier_quadratic_curve_casteljau(p1, p2, p3, draw_list_shared_data.get_curve_tessellation_tolerance(), 0); + } + else + { + path_reserve_extra(segments); + const auto step = 1.f / static_cast(segments); + for (std::uint32_t i = 1; i <= segments; ++i) + { + path_pin(bezier_quadratic_calc(p1, p2, p3, step * static_cast(i))); + } + } + } + + DrawList::DrawList() noexcept + : draw_list_flag_{DrawListFlag::NONE}, + this_command_clip_rect_{0, 0, 0, 0}, + this_command_texture_id_{0} + { + // reset(); + } + + auto DrawList::draw_list_flag(const DrawListFlag flag) noexcept -> void + { + draw_list_flag_ = flag; + } + + auto DrawList::draw_list_flag(const std::underlying_type_t flag) noexcept -> void + { + draw_list_flag(static_cast(flag)); + } + + auto DrawList::reset() noexcept -> void + { + const auto& font = Context::instance().font(); + + auto& [command_list, vertex_list, index_list] = private_data_; + + command_list.clear(); + vertex_list.resize(0); + index_list.resize(0); + + // we don't know the size of the clip rect, so we need the user to set it + this_command_clip_rect_ = {}; + // the first texture is always the (default) font texture + this_command_texture_id_ = font.texture_id(); + + path_list_.clear(); + + // we always have a command ready in the buffer + command_list.emplace_back( + command_type + { + .clip_rect = this_command_clip_rect_, + .texture_id = this_command_texture_id_, + .index_offset = index_list.size(), + // set by subsequent draw_xxx + .element_count = 0 + } + ); + } + + auto DrawList::push_clip_rect(const rect_type& rect, const bool intersect_with_current_clip_rect) noexcept -> rect_type& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); + + auto& [command_list, _1, _2] = private_data_; + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list.empty()); + + const auto& [current_clip_rect, current_texture, current_index_offset, current_element_count] = command_list.back(); + + this_command_clip_rect_ = intersect_with_current_clip_rect ? rect.combine_min(current_clip_rect) : rect; + + on_element_changed(ChangedElement::CLIP_RECT); + return command_list.back().clip_rect; + } + + auto DrawList::push_clip_rect(const point_type& left_top, const point_type& right_bottom, const bool intersect_with_current_clip_rect) noexcept -> rect_type& + { + return push_clip_rect({left_top, right_bottom}, intersect_with_current_clip_rect); + } + + auto DrawList::pop_clip_rect() noexcept -> void + { + const auto& [command_list, _1, _2] = private_data_; + + // at least one command + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list.size() > 1); + this_command_clip_rect_ = command_list[command_list.size() - 2].clip_rect; + + on_element_changed(ChangedElement::CLIP_RECT); + } + + auto DrawList::push_texture_id(const texture_id_type texture) noexcept -> void + { + this_command_texture_id_ = texture; + + on_element_changed(ChangedElement::TEXTURE_ID); + } + + auto DrawList::pop_texture_id() noexcept -> void + { + const auto& [command_list, _1, _2] = private_data_; + + // at least one command + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list.size() > 1); + this_command_texture_id_ = command_list[command_list.size() - 2].texture_id; + + on_element_changed(ChangedElement::TEXTURE_ID); + } + + auto DrawList::line( + const point_type& from, + const point_type& to, + const color_type& color, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + // path_pin(from + point_type{.5f, .5f}); + // path_pin(to + point_type{.5f, .5f}); + path_pin(from); + path_pin(to); + + path_stroke(color, DrawFlag::NONE, thickness); + } + + + auto DrawList::triangle( + const point_type& a, + const point_type& b, + const point_type& c, + const color_type& color, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + path_pin(a); + path_pin(b); + path_pin(c); + + path_stroke(color, DrawFlag::CLOSED, thickness); + } + + auto DrawList::triangle_filled( + const point_type& a, + const point_type& b, + const point_type& c, + const color_type& color + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + path_pin(a); + path_pin(b); + path_pin(c); + + path_stroke(color); + } + + auto DrawList::rect( + const rect_type& rect, + const color_type& color, + const float rounding, + const DrawFlag flag, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + // path_rect(rect_type{rect.left_top() + point_type{.5f, .5f}, rect.right_bottom() - point_type{.5f, .5f}}, rounding, flag); + path_rect(rect, rounding, flag); + + path_stroke(color, DrawFlag::CLOSED, thickness); + } + + auto DrawList::rect( + const point_type& left_top, + const point_type& right_bottom, + const color_type& color, + const float rounding, + const DrawFlag flag, + const float thickness + ) noexcept -> void + { + return rect({left_top, right_bottom}, color, rounding, flag, thickness); + } + + auto DrawList::rect_filled( + const rect_type& rect, + const color_type& color, + const float rounding, + const DrawFlag flag + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + if (rounding < .5f or (DrawFlag::ROUND_CORNER_MASK & flag) == DrawFlag::ROUND_CORNER_NONE) + { + draw_rect_filled(rect, color, color, color, color); + } + else + { + path_rect(rect, rounding, flag); + path_stroke(color); + } + } + + auto DrawList::rect_filled( + const point_type& left_top, + const point_type& right_bottom, + const color_type& color, + const float rounding, + const DrawFlag flag + ) noexcept -> void + { + return rect_filled({left_top, right_bottom}, color, rounding, flag); + } + + auto DrawList::rect_filled( + const rect_type& rect, + const color_type& color_left_top, + const color_type& color_right_top, + const color_type& color_left_bottom, + const color_type& color_right_bottom + ) noexcept -> void + { + if (color_left_top.alpha == 0 or color_right_top.alpha == 0 or color_left_bottom.alpha == 0 or color_right_bottom.alpha == 0) + { + return; + } + + draw_rect_filled(rect, color_left_top, color_right_top, color_left_bottom, color_right_bottom); + } + + auto DrawList::rect_filled( + const point_type& left_top, + const point_type& right_bottom, + const color_type& color_left_top, + const color_type& color_right_top, + const color_type& color_left_bottom, + const color_type& color_right_bottom + ) noexcept -> void + { + return rect_filled({left_top, right_bottom}, color_left_top, color_right_top, color_left_bottom, color_right_bottom); + } + + auto DrawList::quadrilateral( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type& color, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + path_quadrilateral(p1, p2, p3, p4); + + path_stroke(color, DrawFlag::CLOSED, thickness); + } + + auto DrawList::quadrilateral_filled( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type& color + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + path_quadrilateral(p1, p2, p3, p4); + + path_stroke(color); + } + + auto DrawList::circle_n( + const circle_type& circle, + const color_type& color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0 or circle.radius < .5f or segments < 3) + { + return; + } + + path_arc_n(circle, 0, std::numbers::pi_v * 2, segments); + + path_stroke(color, DrawFlag::CLOSED, thickness); + } + + auto DrawList::circle_n( + const point_type& center, + const float radius, + const color_type& color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + return circle_n({center, radius}, color, segments, thickness); + } + + auto DrawList::ellipse_n( + const ellipse_type& ellipse, + const color_type& color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f or segments < 3) + { + return; + } + + path_arc_elliptical_n(ellipse, 0, std::numbers::pi_v * 2, segments); + + path_stroke(color, DrawFlag::CLOSED, thickness); + } + + auto DrawList::ellipse_n( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type& color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + return ellipse_n({center, radius, rotation}, color, segments, thickness); + } + + auto DrawList::circle_n_filled( + const circle_type& circle, + const color_type& color, + const std::uint32_t segments + ) noexcept -> void + { + if (color.alpha == 0 or circle.radius < .5f or segments < 3) + { + return; + } + + path_arc_n(circle, 0, std::numbers::pi_v * 2, segments); + + path_stroke(color); + } + + auto DrawList::circle_n_filled( + const point_type& center, + const float radius, + const color_type& color, + const std::uint32_t segments + ) noexcept -> void + { + return circle_n_filled({center, radius}, color, segments); + } + + auto DrawList::ellipse_n_filled( + const ellipse_type& ellipse, + const color_type& color, + const std::uint32_t segments + ) noexcept -> void + { + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f or segments < 3) + { + return; + } + + path_arc_elliptical_n(ellipse, 0, std::numbers::pi_v * 2, segments); + + path_stroke(color); + } + + auto DrawList::ellipse_n_filled( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type& color, + const std::uint32_t segments + ) noexcept -> void + { + return ellipse_n_filled({center, radius, rotation}, color, segments); + } + + auto DrawList::circle( + const circle_type& circle, + const color_type& color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0 or circle.radius < .5f) + { + return; + } + + if (segments == 0) + { + path_arc_fast(circle, 0, DrawListSharedData::vertex_sample_points_count - 1); + + path_stroke(color, DrawFlag::CLOSED, thickness); + } + else + { + const auto clamped_segments = std::ranges::clamp(segments, DrawListSharedData::circle_segments_min, DrawListSharedData::circle_segments_max); + + circle_n(circle, color, clamped_segments, thickness); + } + } + + auto DrawList::circle( + const point_type& center, + const float radius, + const color_type& color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + return circle({center, radius}, color, segments, thickness); + } + + auto DrawList::circle_filled( + const circle_type& circle, + const color_type& color, + const std::uint32_t segments + ) noexcept -> void + { + if (color.alpha == 0 or circle.radius < .5f) + { + return; + } + + if (segments == 0) + { + path_arc_fast(circle, 0, DrawListSharedData::vertex_sample_points_count - 1); + + path_stroke(color); + } + else + { + const auto clamped_segments = std::ranges::clamp(segments, DrawListSharedData::circle_segments_min, DrawListSharedData::circle_segments_max); + + circle_n_filled(circle, color, clamped_segments); + } + } + + auto DrawList::circle_filled( + const point_type& center, + const float radius, + const color_type& color, + const std::uint32_t segments + ) noexcept -> void + { + return circle_filled({center, radius}, color, segments); + } + + auto DrawList::ellipse( + const ellipse_type& ellipse, + const color_type& color, + std::uint32_t segments, + const float thickness + ) noexcept -> void + { + const auto& draw_list_shared_data = Context::instance().draw_list_shared_data(); + + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) + { + return; + } + + if (segments == 0) + { + // fixme: maybe there's a better computation to do here + segments = draw_list_shared_data.get_circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); + } + + ellipse_n(ellipse, color, segments, thickness); + } + + auto DrawList::ellipse( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type& color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + return ellipse({center, radius, rotation}, color, segments, thickness); + } + + auto DrawList::ellipse_filled( + const ellipse_type& ellipse, + const color_type& color, + std::uint32_t segments + ) noexcept -> void + { + const auto& draw_list_shared_data = Context::instance().draw_list_shared_data(); + + if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) + { + return; + } + + if (segments == 0) + { + // fixme: maybe there's a better computation to do here + segments = draw_list_shared_data.get_circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); + } + + ellipse_n_filled(ellipse, color, segments); + } + + auto DrawList::ellipse_filled( + const point_type& center, + const extent_type& radius, + const float rotation, + const color_type& color, + const std::uint32_t segments + ) noexcept -> void + { + return ellipse_filled({center, radius, rotation}, color, segments); + } + + auto DrawList::bezier_cubic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type& color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + path_bezier_curve(p1, p2, p3, p4, segments); + + path_stroke(color, DrawFlag::NONE, thickness); + } + + auto DrawList::bezier_quadratic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const color_type& color, + const std::uint32_t segments, + const float thickness + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + path_bezier_quadratic_curve(p1, p2, p3, segments); + + path_stroke(color, DrawFlag::NONE, thickness); + } + + auto DrawList::text( + const Font& font, + const float font_size, + const point_type& p, + const color_type& color, + const std::string_view utf8_text, + const float wrap_width + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + draw_text(font, font_size, p, color, utf8_text, wrap_width); + } + + auto DrawList::text( + const float font_size, + const point_type& p, + const color_type& color, + const std::string_view utf8_text, + const float wrap_width + ) noexcept -> void + { + const auto& font = Context::instance().font(); + + this->text(font, font_size, p, color, utf8_text, wrap_width); + } + + auto DrawList::image( + const texture_id_type texture_id, + const point_type& display_p1, + const point_type& display_p2, + const point_type& display_p3, + const point_type& display_p4, + const uv_type& uv_p1, + const uv_type& uv_p2, + const uv_type& uv_p3, + const uv_type& uv_p4, + const color_type& color + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + draw_image(texture_id, display_p1, display_p2, display_p3, display_p4, uv_p1, uv_p2, uv_p3, uv_p4, color); + } + + auto DrawList::image( + const texture_id_type texture_id, + const rect_type& display_rect, + const rect_type& uv_rect, + const color_type& color + ) noexcept -> void + { + image( + texture_id, + display_rect.left_top(), + display_rect.right_top(), + display_rect.right_bottom(), + display_rect.left_bottom(), + uv_rect.left_top(), + uv_rect.right_top(), + uv_rect.right_bottom(), + uv_rect.left_bottom(), + color + ); + } + + auto DrawList::image( + const texture_id_type texture_id, + const point_type& display_left_top, + const point_type& display_right_bottom, + const uv_type& uv_left_top, + const uv_type& uv_right_bottom, + const color_type& color + ) noexcept -> void + { + image(texture_id, {display_left_top, display_right_bottom}, {uv_left_top, uv_right_bottom}, color); + } + + auto DrawList::image_rounded( + const texture_id_type texture_id, + const rect_type& display_rect, + const float rounding, + const DrawFlag flag, + const rect_type& uv_rect, + const color_type& color + ) noexcept -> void + { + if (color.alpha == 0) + { + return; + } + + draw_image_rounded(texture_id, display_rect, uv_rect, color, rounding, flag); + } + + auto DrawList::image_rounded( + const texture_id_type texture_id, + const point_type& display_left_top, + const point_type& display_right_bottom, + const float rounding, + const DrawFlag flag, + const uv_type& uv_left_top, + const uv_type& uv_right_bottom, + const color_type& color + ) noexcept -> void + { + image_rounded(texture_id, {display_left_top, display_right_bottom}, rounding, flag, {uv_left_top, uv_right_bottom}, color); + } +} diff --git a/src/draw/draw_list.hpp b/src/draw/draw_list.hpp new file mode 100644 index 00000000..fe3454b7 --- /dev/null +++ b/src/draw/draw_list.hpp @@ -0,0 +1,569 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include + +namespace gal::prometheus::draw +{ + class Font; + + class DrawList final + { + public: + template + using container_type = DrawListDef::container_type; + + // ---------------------------------------------------- + + using rect_type = DrawListDef::rect_type; + using point_type = DrawListDef::point_type; + using extent_type = DrawListDef::extent_type; + + using circle_type = DrawListDef::circle_type; + using ellipse_type = DrawListDef::ellipse_type; + + // ---------------------------------------------------- + + using uv_type = DrawListDef::uv_type; + using color_type = DrawListDef::color_type; + using vertex_type = DrawListDef::vertex_type; + using index_type = DrawListDef::index_type; + + // ---------------------------------------------------- + + using path_list_type = DrawListDef::path_list_type; + using vertex_list_type = DrawListDef::vertex_list_type; + using index_list_type = DrawListDef::index_list_type; + + // ---------------------------------------------------- + + using texture_id_type = DrawListDef::texture_id_type; + using size_type = DrawListDef::size_type; + + using command_type = DrawListDef::command_type; + using command_list_type = DrawListDef::command_list_type; + + private: + DrawListFlag draw_list_flag_; + + /** + * @brief This wrapper structure expects to limit the data writes to a limited number of functions to avoid unintended data writes. + * @see make_accessor + * @see push_command + * @see on_element_changed + * @see reset + * @see push_clip_rect + * @see draw_image_rounded (fill image uv) + */ + struct private_data_type + { + // vertex_list: v1-v2-v3-v4 + v5-v6-v7-v8 + v9-v10-v11 => rect0 + rect1(clipped by rect0) + triangle0(clipped by rect1) + // index_list: 0/1/2-0/2/3 + 4/5/6-4/6/7 + 8/9/10 + // command_list: + // 0: .clip_rect = {0, 0, root_window_width, root_window_height}, .index_offset = 0, .element_count = root_window_element_count + 6 (two triangles => 0/1/2-0/2/3) + // 1: .clip_rect = {max(rect0.left, rect1.left), max(rect0.top, rect1.top), min(rect0.right, rect1.right), min(rect0.bottom, rect1.bottom)}, .index_offset = root_window_element_count + 6, .element_count = 6 (two triangles => 4/5/6-4/6/7) + // 2: .clip_rect = {...}, .index_offset = root_window_element_count + 12, .element_count = 3 (one triangle => 8/9/10) + command_list_type command_list; + vertex_list_type vertex_list; + index_list_type index_list; + }; + + private_data_type private_data_; + + rect_type this_command_clip_rect_; + texture_id_type this_command_texture_id_; + + path_list_type path_list_; + + [[nodiscard]] auto make_accessor() noexcept -> DrawListDef::Accessor; + + auto push_command() noexcept -> void; + + enum class ChangedElement : std::uint8_t + { + CLIP_RECT, + TEXTURE_ID, + }; + + auto on_element_changed(ChangedElement element) noexcept -> void; + + // ---------------------------------------------------------------------------- + // DRAW + + auto draw_polygon_line(const color_type& color, DrawFlag draw_flag, float thickness) noexcept -> void; + + auto draw_polygon_line_aa(const color_type& color, DrawFlag draw_flag, float thickness) noexcept -> void; + + auto draw_convex_polygon_line_filled(const color_type& color) noexcept -> void; + + auto draw_convex_polygon_line_filled_aa(const color_type& color) noexcept -> void; + + auto draw_rect_filled( + const rect_type& rect, + const color_type& color_left_top, + const color_type& color_right_top, + const color_type& color_left_bottom, + const color_type& color_right_bottom + ) noexcept -> void; + + auto draw_text( + const Font& font, + float font_size, + const point_type& p, + const color_type& color, + std::string_view utf8_text, + float wrap_width + ) noexcept -> void; + + auto draw_image( + texture_id_type texture_id, + const point_type& display_p1, + const point_type& display_p2, + const point_type& display_p3, + const point_type& display_p4, + const uv_type& uv_p1, + const uv_type& uv_p2, + const uv_type& uv_p3, + const uv_type& uv_p4, + const color_type& color + ) noexcept -> void; + + auto draw_image_rounded( + texture_id_type texture_id, + const rect_type& display_rect, + const rect_type& uv_rect, + const color_type& color, + float rounding, + DrawFlag flag + ) noexcept -> void; + + // ---------------------------------------------------------------------------- + // PATH + + constexpr auto path_clear() noexcept -> void + { + path_list_.clear(); + } + + constexpr auto path_reserve(const std::size_t size) noexcept -> void + { + path_list_.reserve(size); + } + + constexpr auto path_reserve_extra(const std::size_t size) noexcept -> void + { + path_reserve(path_list_.size() + size); + } + + constexpr auto path_pin(const point_type& point) noexcept -> void + { + path_list_.push_back(point); + } + + auto path_stroke(const color_type& color, DrawFlag flag, float thickness) noexcept -> void; + + auto path_stroke(const color_type& color) noexcept -> void; + + auto path_arc_fast(const circle_type& circle, int from, int to) noexcept -> void; + + auto path_arc_fast(const circle_type& circle, DrawArcFlag flag) noexcept -> void; + + auto path_arc_n(const circle_type& circle, float from, float to, std::uint32_t segments) noexcept -> void; + + auto path_arc(const circle_type& circle, float from, float to) noexcept -> void; + + auto path_arc_elliptical_n(const ellipse_type& ellipse, float from, float to, std::uint32_t segments) noexcept -> void; + + auto path_quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> void; + + auto path_rect(const rect_type& rect, float rounding, DrawFlag flag) noexcept -> void; + + auto path_bezier_cubic_curve_casteljau( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + float tessellation_tolerance, + std::size_t level + ) noexcept -> void; + + auto path_bezier_quadratic_curve_casteljau( + const point_type& p1, + const point_type& p2, + const point_type& p3, + float tessellation_tolerance, + std::size_t level + ) noexcept -> void; + + auto path_bezier_curve( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + std::uint32_t segments + ) noexcept -> void; + + auto path_bezier_quadratic_curve( + const point_type& p1, + const point_type& p2, + const point_type& p3, + std::uint32_t segments + ) noexcept -> void; + + public: + /** + * @note The default font may not be set at this point, so we must manually call @c reset before using it. + */ + DrawList() noexcept; + + // ---------------------------------------------------------------------------- + // FLAG + + auto draw_list_flag(DrawListFlag flag) noexcept -> void; + + auto draw_list_flag(std::underlying_type_t flag) noexcept -> void; + + // ---------------------------------------------------------------------------- + // RESET + + auto reset() noexcept -> void; + + // ---------------------------------------------------------------------------- + // DRAW DATA + + [[nodiscard]] constexpr auto command_list() const noexcept -> auto + { + return private_data_.command_list | std::views::all; + } + + [[nodiscard]] constexpr auto vertex_list() const noexcept -> auto + { + return private_data_.vertex_list | std::views::all; + } + + [[nodiscard]] constexpr auto index_list() const noexcept -> auto + { + return private_data_.index_list | std::views::all; + } + + // ---------------------------------------------------------------------------- + // CLIP RECT & TEXTURE + + auto push_clip_rect(const rect_type& rect, bool intersect_with_current_clip_rect) noexcept -> rect_type&; + + auto push_clip_rect(const point_type& left_top, const point_type& right_bottom, bool intersect_with_current_clip_rect) noexcept -> rect_type&; + + auto pop_clip_rect() noexcept -> void; + + auto push_texture_id(texture_id_type texture) noexcept -> void; + + auto pop_texture_id() noexcept -> void; + + // ---------------------------------------------------------------------------- + // PRIMITIVE + + auto line( + const point_type& from, + const point_type& to, + const color_type& color, + float thickness = 1.f + ) noexcept -> void; + + auto triangle( + const point_type& a, + const point_type& b, + const point_type& c, + const color_type& color, + float thickness = 1.f + ) noexcept -> void; + + auto triangle_filled( + const point_type& a, + const point_type& b, + const point_type& c, + const color_type& color + ) noexcept -> void; + + auto rect( + const rect_type& rect, + const color_type& color, + float rounding = .0f, + DrawFlag flag = DrawFlag::NONE, + float thickness = 1.f + ) noexcept -> void; + + auto rect( + const point_type& left_top, + const point_type& right_bottom, + const color_type& color, + float rounding = .0f, + DrawFlag flag = DrawFlag::NONE, + float thickness = 1.f + ) noexcept -> void; + + auto rect_filled( + const rect_type& rect, + const color_type& color, + float rounding = .0f, + DrawFlag flag = DrawFlag::NONE + ) noexcept -> void; + + auto rect_filled( + const point_type& left_top, + const point_type& right_bottom, + const color_type& color, + float rounding = .0f, + DrawFlag flag = DrawFlag::NONE + ) noexcept -> void; + + auto rect_filled( + const rect_type& rect, + const color_type& color_left_top, + const color_type& color_right_top, + const color_type& color_left_bottom, + const color_type& color_right_bottom + ) noexcept -> void; + + auto rect_filled( + const point_type& left_top, + const point_type& right_bottom, + const color_type& color_left_top, + const color_type& color_right_top, + const color_type& color_left_bottom, + const color_type& color_right_bottom + ) noexcept -> void; + + auto quadrilateral( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type& color, + float thickness = 1.f + ) noexcept -> void; + + auto quadrilateral_filled( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type& color + ) noexcept -> void; + + auto circle_n( + const circle_type& circle, + const color_type& color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto circle_n( + const point_type& center, + float radius, + const color_type& color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse_n( + const ellipse_type& ellipse, + const color_type& color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse_n( + const point_type& center, + const extent_type& radius, + float rotation, + const color_type& color, + std::uint32_t segments, + float thickness = 1.f + ) noexcept -> void; + + auto circle_n_filled( + const circle_type& circle, + const color_type& color, + std::uint32_t segments + ) noexcept -> void; + + auto circle_n_filled( + const point_type& center, + float radius, + const color_type& color, + std::uint32_t segments + ) noexcept -> void; + + auto ellipse_n_filled( + const ellipse_type& ellipse, + const color_type& color, + std::uint32_t segments + ) noexcept -> void; + + auto ellipse_n_filled( + const point_type& center, + const extent_type& radius, + float rotation, + const color_type& color, + std::uint32_t segments + ) noexcept -> void; + + auto circle( + const circle_type& circle, + const color_type& color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto circle( + const point_type& center, + float radius, + const color_type& color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto circle_filled( + const circle_type& circle, + const color_type& color, + std::uint32_t segments = 0 + ) noexcept -> void; + + auto circle_filled( + const point_type& center, + float radius, + const color_type& color, + std::uint32_t segments = 0 + ) noexcept -> void; + + auto ellipse( + const ellipse_type& ellipse, + const color_type& color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse( + const point_type& center, + const extent_type& radius, + float rotation, + const color_type& color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto ellipse_filled( + const ellipse_type& ellipse, + const color_type& color, + std::uint32_t segments = 0 + ) noexcept -> void; + + auto ellipse_filled( + const point_type& center, + const extent_type& radius, + float rotation, + const color_type& color, + std::uint32_t segments = 0 + ) noexcept -> void; + + auto bezier_cubic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const point_type& p4, + const color_type& color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + auto bezier_quadratic( + const point_type& p1, + const point_type& p2, + const point_type& p3, + const color_type& color, + std::uint32_t segments = 0, + float thickness = 1.f + ) noexcept -> void; + + // ---------------------------------------------------------------------------- + // TEXT + + auto text( + const Font& font, + float font_size, + const point_type& p, + const color_type& color, + std::string_view utf8_text, + float wrap_width = std::numeric_limits::max() + ) noexcept -> void; + + auto text( + float font_size, + const point_type& p, + const color_type& color, + std::string_view utf8_text, + float wrap_width = std::numeric_limits::max() + ) noexcept -> void; + + // ---------------------------------------------------------------------------- + // IMAGE + + // p1________ p2 + // | | + // | | + // p4|_______| p3 + auto image( + texture_id_type texture_id, + const point_type& display_p1, + const point_type& display_p2, + const point_type& display_p3, + const point_type& display_p4, + const uv_type& uv_p1 = {0, 0}, + const uv_type& uv_p2 = {1, 0}, + const uv_type& uv_p3 = {1, 1}, + const uv_type& uv_p4 = {0, 1}, + const color_type& color = primitive::colors::white + ) noexcept -> void; + + auto image( + texture_id_type texture_id, + const rect_type& display_rect, + const rect_type& uv_rect = {0, 0, 1, 1}, + const color_type& color = primitive::colors::white + ) noexcept -> void; + + auto image( + texture_id_type texture_id, + const point_type& display_left_top, + const point_type& display_right_bottom, + const uv_type& uv_left_top = {0, 0}, + const uv_type& uv_right_bottom = {1, 1}, + const color_type& color = primitive::colors::white + ) noexcept -> void; + + auto image_rounded( + texture_id_type texture_id, + const rect_type& display_rect, + float rounding = .0f, + DrawFlag flag = DrawFlag::NONE, + const rect_type& uv_rect = {0, 0, 1, 1}, + const color_type& color = primitive::colors::white + ) noexcept -> void; + + auto image_rounded( + texture_id_type texture_id, + const point_type& display_left_top, + const point_type& display_right_bottom, + float rounding = .0f, + DrawFlag flag = DrawFlag::NONE, + const uv_type& uv_left_top = {0, 0}, + const uv_type& uv_right_bottom = {1, 1}, + const color_type& color = primitive::colors::white + ) noexcept -> void; + }; +} diff --git a/src/draw/flag.hpp b/src/draw/flag.hpp new file mode 100644 index 00000000..03f390b8 --- /dev/null +++ b/src/draw/flag.hpp @@ -0,0 +1,162 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include + +#include + +namespace gal::prometheus::draw +{ + enum class DrawFlag : std::uint8_t + { + NONE = 0, + // specify that shape should be closed + // @see DrawList::draw_polygon_line + // @see DrawList::draw_polygon_line_aa + // @see DrawList::path_stroke + CLOSED = 1 << 0, + // enable rounding left-top corner only (when rounding > 0.0f, we default to all corners) + // @see DrawList::path_rect + // @see DrawList::rect + // @see DrawList::rect_filled + ROUND_CORNER_LEFT_TOP = 1 << 1, + // enable rounding right_top corner only (when rounding > 0.0f, we default to all corners) + // @see DrawList::path_rect + // @see DrawList::rect + // @see DrawList::rect_filled + ROUND_CORNER_RIGHT_TOP = 1 << 2, + // enable rounding left-bottom corner only (when rounding > 0.0f, we default to all corners) + // @see DrawList::path_rect + // @see DrawList::rect + // @see DrawList::rect_filled + ROUND_CORNER_LEFT_BOTTOM = 1 << 3, + // enable rounding right-bottom corner only (when rounding > 0.0f, we default to all corners) + // @see DrawList::path_rect + // @see DrawList::rect + // @see DrawList::rect_filled + ROUND_CORNER_RIGHT_BOTTOM = 1 << 4, + // disable rounding on all corners (when rounding > 0.0f) + ROUND_CORNER_NONE = 1 << 5, + + ROUND_CORNER_LEFT = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_LEFT_BOTTOM, + ROUND_CORNER_TOP = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP, + ROUND_CORNER_RIGHT = ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_RIGHT_BOTTOM, + ROUND_CORNER_BOTTOM = ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, + + ROUND_CORNER_ALL = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, + ROUND_CORNER_DEFAULT = ROUND_CORNER_ALL, + ROUND_CORNER_MASK = ROUND_CORNER_ALL | ROUND_CORNER_NONE, + + PROMETHEUS_MAGIC_ENUM_FLAG = std::numeric_limits::max(), + }; + + enum class DrawListFlag : std::uint8_t + { + NONE = 0, + ANTI_ALIASED_LINE = 1 << 0, + ANTI_ALIASED_LINE_USE_TEXTURE = 1 << 1, + ANTI_ALIASED_FILL = 1 << 2, + + PROMETHEUS_MAGIC_ENUM_FLAG = std::numeric_limits::max(), + }; + + enum class DrawArcFlag : std::uint8_t + { + // [0~3) + Q1 = 1 << 0, + // [3~6) + Q2 = 1 << 1, + // [6~9) + Q3 = 1 << 2, + // [9~12) + Q4 = 1 << 3, + + RIGHT_TOP = Q1, + LEFT_TOP = Q2, + LEFT_BOTTOM = Q3, + RIGHT_BOTTOM = Q4, + TOP = Q1 | Q2, + BOTTOM = Q3 | Q4, + LEFT = Q2 | Q3, + RIGHT = Q1 | Q4, + ALL = Q1 | Q2 | Q3 | Q4, + + // [3, 0) + Q1_CLOCK_WISH = 1 << 4, + // [6, 3) + Q2_CLOCK_WISH = 1 << 5, + // [9, 6) + Q3_CLOCK_WISH = 1 << 6, + // [12, 9) + Q4_CLOCK_WISH = 1 << 7, + + RIGHT_TOP_CLOCK_WISH = Q1_CLOCK_WISH, + LEFT_TOP_CLOCK_WISH = Q2_CLOCK_WISH, + LEFT_BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH, + RIGHT_BOTTOM_CLOCK_WISH = Q4_CLOCK_WISH, + TOP_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH, + BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH | Q4_CLOCK_WISH, + LEFT_CLOCK_WISH = Q2_CLOCK_WISH | Q3_CLOCK_WISH, + RIGHT_CLOCK_WISH = Q1_CLOCK_WISH | Q4_CLOCK_WISH, + ALL_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH | Q3_CLOCK_WISH | Q4_CLOCK_WISH, + + PROMETHEUS_MAGIC_ENUM_FLAG = std::numeric_limits::max(), + }; + + [[nodiscard]] auto range_of_arc_quadrant(DrawArcFlag quadrant) noexcept -> std::pair; + + enum class ThemeCategory : std::uint8_t + { + TEXT = 0, + BORDER, + + WINDOW_BACKGROUND, + + WIDGET_BACKGROUND, + WIDGET_ACTIVATED, + + TITLE_BAR, + TITLE_BAR_COLLAPSED, + + SLIDER, + SLIDER_ACTIVATED, + + BUTTON, + BUTTON_HOVERED, + BUTTON_ACTIVATED, + + RESIZE_GRIP, + RESIZE_GRIP_HOVERED, + RESIZE_GRIP_ACTIVATED, + + TOOLTIP_BACKGROUND, + TOOLTIP_TEXT, + + // ------------------------------- + INTERNAL_COUNT + }; + + constexpr auto theme_category_count = static_cast(ThemeCategory::INTERNAL_COUNT); + + enum class WindowFlag : std::uint8_t + { + NONE = 0, + + BORDERED = 1 << 0, + + NO_TITLE_BAR = 1 << 1, + // Meaningful if and only if NO_TITLE_BAR is not set + NO_CLOSE = 1 << 2, + NO_RESIZE = 1 << 3, + NO_MOVE = 1 << 4, + + PROMETHEUS_MAGIC_ENUM_FLAG = std::numeric_limits::max(), + }; +} diff --git a/src/draw/font.cpp b/src/draw/font.cpp new file mode 100644 index 00000000..2c6b318c --- /dev/null +++ b/src/draw/font.cpp @@ -0,0 +1,548 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include FT_FREETYPE_H // +// #include FT_MODULE_H // +// #include FT_GLYPH_H // +// #include FT_SYNTHESIS_H // + +#define STB_RECT_PACK_IMPLEMENTATION +#include + +#include +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace +{ + struct ft_type + { + FT_Library library; + FT_Face face; + + [[nodiscard]] constexpr auto valid() const noexcept -> bool + { + return library and face; + } + }; + + [[nodiscard]] auto create_ft(const std::string_view font_path, const std::uint32_t pixel_height) noexcept -> ft_type + { + FT_Library ft_library; + if (FT_Init_FreeType(&ft_library)) + { + // Could not initialize FreeType library + return {.library = nullptr, .face = nullptr}; + } + + FT_Face ft_face; + if (FT_New_Face(ft_library, font_path.data(), 0, &ft_face)) + { + FT_Done_FreeType(ft_library); + // Could not load font + return {.library = nullptr, .face = nullptr}; + } + + FT_Set_Pixel_Sizes(ft_face, 0, pixel_height); + return {.library = ft_library, .face = ft_face}; + } + + auto destroy_ft(const ft_type ft) noexcept -> void + { + auto [ft_library, ft_face] = ft; + + FT_Done_Face(ft_face); + FT_Done_FreeType(ft_library); + } +} + +namespace gal::prometheus::draw +{ + Font::Texture::~Texture() noexcept + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(id_ != invalid_texture_id, "Texture is not bound to a GPU resource id!"); + } + + auto Font::Texture::bind(const texture_id_type id) const noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid(), "Only valid textures can be bound to a GPU resource id"); + + id_.get() = id; + } + + auto Font::reset() noexcept -> void + { + font_path_.clear(); + pixel_height_ = std::numeric_limits::min(); + baked_line_max_width_ = std::numeric_limits::min(); + + glyphs_.clear(); + fallback_glyph_ = {}; + + white_pixel_uv_ = {}; + baked_line_uv_ = {}; + + texture_id_ = invalid_texture_id; + } + + Font::Font() noexcept + { + reset(); + } + + Font::~Font() noexcept = default; + + auto Font::option() noexcept -> Option + { + return {}; + } + + auto Font::load(const Option& option) noexcept -> Texture + { + reset(); + + font_path_ = std::format("{}-{}px", option.font_path_, option.pixel_height_); + pixel_height_ = option.pixel_height_; + + if (option.baked_line_max_width_ == 0 or option.baked_line_max_width_ > default_baked_line_max_width) + { + baked_line_max_width_ = default_baked_line_max_width; + } + else + { + baked_line_max_width_ = option.baked_line_max_width_; + } + + Texture texture{texture_id_}; + + auto ft = create_ft(option.font_path_, option.pixel_height_); + if (not ft.valid()) + { + return texture; + } + + auto [library, face] = ft; + + // =============================== + + std::vector rects; + + // baked line + constexpr auto id_baked_line = std::numeric_limits::min() + 0; + { + const auto baked_line_uv_width = baked_line_max_width_ + 1; + const auto baked_line_uv_height = baked_line_max_width_ + 2; + + baked_line_uv_.reserve(baked_line_uv_height); + + rects.emplace_back( + stbrp_rect + { + .id = id_baked_line, + .w = static_cast(baked_line_uv_width), + .h = static_cast(baked_line_uv_height), + .x = 0, + .y = 0, + .was_packed = 0 + } + ); + } + + // =============================== + + std::ranges::for_each( + option.glyph_ranges_, + [&face, &rects](const auto& pair) noexcept -> void + { + const auto [from, to] = pair; + + for (auto c = from; c <= to; ++c) + { + if (FT_Load_Char(face, c, FT_LOAD_RENDER)) + { + continue; + } + + const auto& g = face->glyph; + rects.emplace_back( + stbrp_rect + { + .id = std::bit_cast(c), + .w = static_cast(g->bitmap.width), + .h = static_cast(g->bitmap.rows), + .x = static_cast(g->bitmap_left), + .y = static_cast(g->bitmap_top), + .was_packed = 0 + } + ); + } + } + ); + + // =============================== + + // todo: texture size? + const auto size = [&rects]() + { + Texture::size_type total_area = 0; + Texture::size_type max_width = 0; + Texture::size_type max_height = 0; + + for (const auto& [id, w, h, x, y, was_packed]: rects) + { + total_area += w * h; + max_width = std::ranges::max(max_width, static_cast(w)); + max_height = std::ranges::max(max_height, static_cast(h)); + } + + const auto min_side = static_cast(std::sqrt(total_area)); + const auto max_side = std::ranges::max(max_width, max_height); + + return std::bit_ceil(std::ranges::max(min_side, max_side)); + }(); + auto atlas_width = size; + auto atlas_height = size; + + stbrp_context context; + std::vector nodes{atlas_width}; + while (true) + { + stbrp_init_target(&context, static_cast(atlas_width), static_cast(atlas_height), nodes.data(), static_cast(nodes.size())); + if (stbrp_pack_rects(&context, rects.data(), static_cast(rects.size()))) + { + break; + } + + atlas_width *= 2; + atlas_height *= 2; + nodes.resize(atlas_width); + } + + // =============================== + + // note: We don't necessarily overwrite all the memory, but it doesn't matter. + // auto texture_data = std::make_unique(static_cast(atlas_width * atlas_height)); + auto texture_data = std::make_unique_for_overwrite(static_cast(atlas_width * atlas_height)); + + const uv_extent_type texture_uv_scale{1.f / static_cast(atlas_width), 1.f / static_cast(atlas_height)}; + + // =============================== + + for (const auto& [id, rect_width, rect_height, rect_x, rect_y, was_packed]: rects) + { + if (id == id_baked_line) + { + using value_type = uv_point_type::value_type; + constexpr std::uint32_t white_color = 0xff'ff'ff'ff; + + // hacky: baked line rect area, one pixel + { + const auto x = rect_x + rect_y * atlas_width; + texture_data[x] = white_color; + + const auto uv_x = static_cast(static_cast(rect_x) + .5f) * texture_uv_scale.width; + const auto uv_y = static_cast(static_cast(rect_y) + .5f) * texture_uv_scale.height; + + white_pixel_uv_ = uv_point_type{uv_x, uv_y}; + } + + // ◿ + for (stbrp_coord y = 0; y < rect_height; ++y) + { + const auto line_width = y; + const auto offset_y = (rect_y + y) * atlas_width; + + for (stbrp_coord x = line_width; x > 0; --x) + { + const auto offset_x = rect_x + (rect_width - x); + const auto index = offset_x + offset_y; + texture_data[index] = white_color; + } + + const auto p_x = rect_x + (rect_width - line_width); + const auto p_y = rect_y + y; + const auto width = line_width; + constexpr auto height = .5f; + + const auto uv_x = static_cast(p_x) * texture_uv_scale.width; + const auto uv_y = static_cast(p_y) * texture_uv_scale.height; + const auto uv_width = static_cast(width) * texture_uv_scale.width; + const auto uv_height = static_cast(height) * texture_uv_scale.height; + + baked_line_uv_.emplace_back(uv_x, uv_y, uv_width, uv_height); + } + + continue; + } + + const auto c = std::bit_cast(id); + + // todo + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(c <= std::numeric_limits::max()); + + if (FT_Load_Char(face, c, FT_LOAD_RENDER)) + { + continue; + } + + const auto& g = face->glyph; + + for (std::uint32_t y = 0; y < g->bitmap.rows; ++y) + { + for (std::uint32_t x = 0; x < g->bitmap.width; ++x) + { + const auto index = rect_x + x + (rect_y + y) * atlas_width; + const auto a = g->bitmap.buffer[x + y * g->bitmap.pitch] << 24; + const auto color = + // A + a | + // B + std::uint32_t{0xff} << 16 | + // G + std::uint32_t{0xff} << 8 | + // R + std::uint32_t{0xff}; + texture_data[index] = color; + } + } + + const auto p_x = static_cast(g->bitmap_left); + const auto p_y = static_cast(g->bitmap_top); + const auto p_width = static_cast(g->bitmap.width); + const auto p_height = static_cast(g->bitmap.rows); + + const auto uv_x = static_cast(rect_x) * texture_uv_scale.width; + const auto uv_y = static_cast(rect_y) * texture_uv_scale.height; + const auto uv_width = static_cast(g->bitmap.width) * texture_uv_scale.width; + const auto uv_height = static_cast(g->bitmap.rows) * texture_uv_scale.height; + + const glyph_type glyph{ + .rect = {p_x, p_y, p_width, p_height}, + .uv = {uv_x, uv_y, uv_width, uv_height}, + .advance_x = static_cast(g->advance.x) / 64.f + }; + + glyphs_.insert_or_assign(static_cast(c), glyph); + } + + fallback_glyph_ = glyphs_[static_cast('?')]; + + // =============================== + + destroy_ft(ft); + + texture.set_size(atlas_width, atlas_height); + texture.set_data(std::move(texture_data)); + return texture; + } + + auto Font::loaded() const noexcept -> bool + { + return not glyphs_.empty() and texture_id_ != invalid_texture_id; + } + + auto Font::font_path() const noexcept -> std::string_view + { + return font_path_; + } + + auto Font::pixel_height() const noexcept -> std::uint32_t + { + return pixel_height_; + } + + auto Font::baked_line_max_width() const noexcept -> std::uint32_t + { + return baked_line_max_width_; + } + + auto Font::glyphs() const noexcept -> const glyphs_type& + { + return glyphs_; + } + + auto Font::fallback_glyph() const noexcept -> const glyph_type& + { + return fallback_glyph_; + } + + auto Font::white_pixel_uv() const noexcept -> const uv_point_type& + { + return white_pixel_uv_; + } + + auto Font::baked_line_uv() const noexcept -> const baked_line_uv_type& + { + return baked_line_uv_; + } + + auto Font::texture_id() const noexcept -> texture_id_type + { + return texture_id_; + } + + auto Font::text_size( + const std::basic_string_view utf8_text, + const float font_size, + const float wrap_width, + std::basic_string& out_text + ) const noexcept -> extent_type + { + // todo + auto utf16_text = chars::convert(utf8_text); + static_assert(std::is_same_v); + + const auto line_height = font_size; + const auto scale = line_height / static_cast(pixel_height_); + const auto& glyphs = glyphs_; + const auto& fallback_glyph = fallback_glyph_; + + float max_width = 0; + float current_width = 0; + float total_height = line_height; + + auto it_input_current = utf16_text.begin(); + const auto it_input_end = utf16_text.end(); + + while (it_input_current != it_input_end) + { + const auto this_char = *it_input_current; + it_input_current += 1; + + if (this_char == u'\n') + { + max_width = std::ranges::max(max_width, current_width); + current_width = 0; + total_height += line_height; + } + else + { + const auto& [glyph_rect, glyph_uv, glyph_advance_x] = [&glyphs, &fallback_glyph](const auto c) -> const auto& { + if (const auto it = glyphs.find(c); + it != glyphs.end()) + { + return it->second; + } + + return fallback_glyph; + }(this_char); + + if (const auto advance_x = glyph_advance_x * scale; + current_width + advance_x > wrap_width) + { + max_width = std::ranges::max(max_width, current_width); + current_width = advance_x; + total_height += line_height; + } + else + { + current_width += advance_x; + } + } + } + + max_width = std::ranges::max(max_width, current_width); + out_text = std::move(utf16_text); + + return {max_width, total_height}; + } + + auto Font::text_size( + const std::basic_string_view utf8_text, + const float font_size, + const float wrap_width + ) const noexcept -> extent_type + { + std::basic_string out; + return text_size(utf8_text, font_size, wrap_width, out); + } + + auto Font::text_draw( + const std::basic_string_view utf8_text, + const float font_size, + const float wrap_width, + const point_type point, + const color_type color, + const DrawListDef::Accessor accessor + ) const noexcept -> void + { + // todo + auto utf16_text = chars::convert(utf8_text); + static_assert(std::is_same_v); + + const auto newline_count = std::ranges::count(utf16_text, u'\n'); + const auto text_size_exclude_newline = utf16_text.size() - newline_count; + + const auto vertex_count = 4 * text_size_exclude_newline; + const auto index_count = 6 * text_size_exclude_newline; + accessor.reserve(vertex_count, index_count); + + const auto line_height = font_size; + const auto scale = line_height / static_cast(pixel_height_); + const auto& glyphs = glyphs_; + const auto& fallback_glyph = fallback_glyph_; + + auto cursor = point + point_type{0, line_height}; + + auto it_input_current = utf16_text.begin(); + const auto it_input_end = utf16_text.end(); + + while (it_input_current != it_input_end) + { + const auto this_char = *it_input_current; + it_input_current += 1; + + if (this_char == u'\n') + { + cursor.x = point.x; + cursor.y += line_height; + continue; + } + + const auto& [glyph_rect, glyph_uv, glyph_advance_x] = [&glyphs, &fallback_glyph](const auto c) -> const auto& { + if (const auto it = glyphs.find(c); + it != glyphs.end()) + { + return it->second; + } + + return fallback_glyph; + }(this_char); + + const auto advance_x = glyph_advance_x * scale; + if (cursor.x + advance_x > point.x + wrap_width) + { + cursor.x = point.x; + cursor.y += line_height; + } + + const rect_type char_rect + { + cursor + point_type{glyph_rect.left_top().x, -glyph_rect.left_top().y} * scale, + glyph_rect.size() * scale + }; + cursor.x += advance_x; + + const auto current_vertex_index = static_cast(accessor.size()); + + accessor.add_vertex(char_rect.left_top(), glyph_uv.left_top(), color); + accessor.add_vertex(char_rect.right_top(), glyph_uv.right_top(), color); + accessor.add_vertex(char_rect.right_bottom(), glyph_uv.right_bottom(), color); + accessor.add_vertex(char_rect.left_bottom(), glyph_uv.left_bottom(), color); + + accessor.add_index(current_vertex_index + 0, current_vertex_index + 1, current_vertex_index + 2); + accessor.add_index(current_vertex_index + 0, current_vertex_index + 2, current_vertex_index + 3); + } + } +} diff --git a/src/draw/font.hpp b/src/draw/font.hpp new file mode 100644 index 00000000..6eece7a8 --- /dev/null +++ b/src/draw/font.hpp @@ -0,0 +1,252 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include + +#include + +#include + +namespace gal::prometheus::draw +{ + // ReSharper disable once CppClassCanBeFinal + class Font + { + public: + using rect_type = DrawListDef::rect_type; + using point_type = DrawListDef::point_type; + using extent_type = DrawListDef::extent_type; + + using color_type = DrawListDef::color_type; + using index_type = DrawListDef::index_type; + + using texture_id_type = DrawListDef::texture_id_type; + constexpr static texture_id_type invalid_texture_id = 0; + + using uv_rect_type = primitive::basic_rect_2d; + using uv_point_type = uv_rect_type::point_type; + using uv_extent_type = uv_rect_type::extent_type; + + // todo: char32_t ? + using char_type = char16_t; + + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH + // Variable '%1$s' is uninitialized. Always initialize a member variable (type.6). + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(26495) + #endif + + struct glyph_type + { + rect_type rect; + uv_rect_type uv; + float advance_x; + }; + + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP + #endif + + using glyphs_type = std::unordered_map; + + using glyph_value_type = i18n::RangeBuilder::value_type; + using glyph_ranges_type = i18n::RangeBuilder::ranges_type; + + using baked_line_uv_type = std::vector; + + // todo + constexpr static std::uint32_t default_baked_line_max_width = 63; + + class Option + { + friend Font; + + std::string font_path_; + glyph_ranges_type glyph_ranges_; + + std::uint32_t pixel_height_; + std::uint32_t baked_line_max_width_; + + Option() noexcept + : pixel_height_{18}, + baked_line_max_width_{default_baked_line_max_width} {} + + public: + constexpr auto path(const std::string_view path) noexcept -> Option& + { + font_path_ = path; + + return *this; + } + + constexpr auto glyph_ranges(glyph_ranges_type&& ranges) noexcept -> Option& + { + glyph_ranges_ = std::move(ranges); + + return *this; + } + + constexpr auto pixel_height(const std::uint32_t height) noexcept -> Option& + { + pixel_height_ = height; + + return *this; + } + + constexpr auto baked_line_max_width(const std::uint32_t width) noexcept -> Option& + { + baked_line_max_width_ = width; + + return *this; + } + }; + + class Texture final + { + friend Font; + + public: + using size_type = std::uint32_t; + // size.width * size.height (RGBA) + using data_type = std::unique_ptr; + + private: + size_type width_; + size_type height_; + data_type data_; + std::reference_wrapper id_; + + explicit Texture(texture_id_type& id) noexcept + : width_{0}, + height_{0}, + data_{nullptr}, + id_{id} {} + + constexpr auto set_size(const size_type width, const size_type height) noexcept -> void + { + width_ = width; + height_ = height; + } + + constexpr auto set_data(data_type&& data) noexcept -> void + { + data_ = std::move(data); + } + + public: + Texture(const Texture& other) = delete; + Texture(Texture&& other) noexcept = default; + auto operator=(const Texture& other) -> Texture& = delete; + auto operator=(Texture&& other) noexcept -> Texture& = default; + + ~Texture() noexcept; + + [[nodiscard]] constexpr auto valid() const noexcept -> bool + { + return data_ != nullptr; + } + + [[nodiscard]] constexpr auto width() const noexcept -> size_type + { + return width_; + } + + [[nodiscard]] constexpr auto height() const noexcept -> size_type + { + return height_; + } + + [[nodiscard]] constexpr auto data() const & noexcept -> const data_type& + { + return data_; + } + + [[nodiscard]] constexpr auto data() && noexcept -> data_type&& + { + return std::move(data_); + } + + auto bind(texture_id_type id) const noexcept -> void; + }; + + private: + std::string font_path_; + std::uint32_t pixel_height_; + std::uint32_t baked_line_max_width_; + + glyphs_type glyphs_; + glyph_type fallback_glyph_; + + uv_point_type white_pixel_uv_; + baked_line_uv_type baked_line_uv_; + + texture_id_type texture_id_; + + auto reset() noexcept -> void; + + public: + explicit Font() noexcept; + + Font(const Font& other) = delete; + Font(Font&& other) noexcept = default; + auto operator=(const Font& other) -> Font& = delete; + auto operator=(Font&& other) noexcept -> Font& = default; + + virtual ~Font() noexcept; + + [[nodiscard]] static auto option() noexcept -> Option; + + [[nodiscard]] auto load(const Option& option) noexcept -> Texture; + + // --------------------------------------------------------- + + [[nodiscard]] auto loaded() const noexcept -> bool; + + [[nodiscard]] auto font_path() const noexcept -> std::string_view; + + [[nodiscard]] auto pixel_height() const noexcept -> std::uint32_t; + + [[nodiscard]] auto baked_line_max_width() const noexcept -> std::uint32_t; + + [[nodiscard]] auto glyphs() const noexcept -> const glyphs_type&; + + [[nodiscard]] auto fallback_glyph() const noexcept -> const glyph_type&; + + [[nodiscard]] auto white_pixel_uv() const noexcept -> const uv_point_type&; + + [[nodiscard]] auto baked_line_uv() const noexcept -> const baked_line_uv_type&; + + [[nodiscard]] auto texture_id() const noexcept -> texture_id_type; + + // ========================================= + // DRAW TEXT + + [[nodiscard]] virtual auto text_size( + std::basic_string_view utf8_text, + float font_size, + float wrap_width, + std::basic_string& out_text + ) const noexcept -> extent_type; + + [[nodiscard]] virtual auto text_size( + std::basic_string_view utf8_text, + float font_size, + float wrap_width + ) const noexcept -> extent_type; + + virtual auto text_draw( + std::basic_string_view utf8_text, + float font_size, + float wrap_width, + point_type point, + color_type color, + DrawListDef::Accessor accessor + ) const noexcept -> void; + }; +} diff --git a/src/draw/mouse.cpp b/src/draw/mouse.cpp new file mode 100644 index 00000000..6e3f7b19 --- /dev/null +++ b/src/draw/mouse.cpp @@ -0,0 +1,97 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +namespace gal::prometheus::draw +{ + Mouse::Mouse(const time_type double_click_interval_threshold, const value_type double_click_distance_threshold) noexcept + : double_click_interval_threshold_{double_click_interval_threshold}, + double_click_distance_threshold_{double_click_distance_threshold}, + position_current_{std::numeric_limits::min(), std::numeric_limits::min()}, + position_previous_{std::numeric_limits::min(), std::numeric_limits::min()}, + position_clicked_{std::numeric_limits::min(), std::numeric_limits::min()}, + down_{false}, + clicked_{false}, + double_clicked_{false}, + pad_{false}, + down_duration_{0}, + click_duration_{0} + { + std::ignore = pad_; + } + + auto Mouse::position() const noexcept -> point_type + { + return position_current_; + } + + auto Mouse::position_delta() const noexcept -> extent_type + { + const auto delta = position_current_ - position_previous_; + + return delta.to(); + } + + auto Mouse::down() const noexcept -> bool + { + return down_; + } + + auto Mouse::clicked() const noexcept -> bool + { + return clicked_; + } + + auto Mouse::double_clicked() const noexcept -> bool + { + return double_clicked_; + } + + auto Mouse::move(const point_type position) noexcept -> void + { + position_current_ = position; + } + + auto Mouse::tick(const time_type tick_time) noexcept -> void + { + position_previous_ = position_current_; + + clicked_ = false; + double_clicked_ = false; + if (down_) + { + if (down_duration_ > 0) + { + down_duration_ += tick_time; + } + else + { + down_duration_ = 0; + clicked_ = true; + } + } + else + { + down_duration_ = std::numeric_limits::min(); + } + if (clicked_) + { + if (0 - click_duration_ < double_click_interval_threshold_) + { + if (position_current_.distance(position_clicked_) < double_click_distance_threshold_) + { + double_clicked_ = true; + } + click_duration_ = std::numeric_limits::min(); + } + else + { + click_duration_ = 0; + position_clicked_ = position_current_; + } + } + } +} diff --git a/src/draw/mouse.hpp b/src/draw/mouse.hpp new file mode 100644 index 00000000..be82a3eb --- /dev/null +++ b/src/draw/mouse.hpp @@ -0,0 +1,69 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +namespace gal::prometheus::draw +{ + class Context; + + class Mouse final + { + friend Context; + + public: + using rect_type = DrawListDef::rect_type; + using point_type = DrawListDef::point_type; + using extent_type = DrawListDef::extent_type; + + using value_type = point_type::value_type; + + using time_type = float; + + private: + // ================================== + // static + // ================================== + + time_type double_click_interval_threshold_; + value_type double_click_distance_threshold_; + + // ================================== + // dynamic + // ================================== + + point_type position_current_; + point_type position_previous_; + point_type position_clicked_; + + bool down_; + bool clicked_; + bool double_clicked_; + bool pad_; + + time_type down_duration_; + time_type click_duration_; + + Mouse(time_type double_click_interval_threshold, value_type double_click_distance_threshold) noexcept; + + public: + [[nodiscard]] auto position() const noexcept -> point_type; + + [[nodiscard]] auto position_delta() const noexcept -> extent_type; + + [[nodiscard]] auto down() const noexcept -> bool; + + [[nodiscard]] auto clicked() const noexcept -> bool; + + [[nodiscard]] auto double_clicked() const noexcept -> bool; + + private: + auto move(point_type position) noexcept -> void; + + auto tick(time_type tick_time) noexcept -> void; + }; +} diff --git a/src/draw/shared_data.cpp b/src/draw/shared_data.cpp new file mode 100644 index 00000000..cd7bb84a --- /dev/null +++ b/src/draw/shared_data.cpp @@ -0,0 +1,165 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include +#include + +#include + +#include + +namespace +{ + using namespace gal::prometheus; + using namespace draw; + + constexpr auto circle_segments_min = DrawListSharedData::circle_segments_min; + constexpr auto circle_segments_max = DrawListSharedData::circle_segments_max; + + // @see https://stackoverflow.com/a/2244088/15194693 + // Number of segments (N) is calculated using equation: + // N = ceil ( pi / acos(1 - error / r) ) where r > 0 and error <= r + [[nodiscard]] constexpr auto circle_segments_calc(const float radius, const float max_error) noexcept -> auto + { + constexpr auto circle_segments_roundup_to_even = [](const auto v) noexcept -> auto + { + return (v + 1) / 2 * 2; + }; + + return std::ranges::clamp( + circle_segments_roundup_to_even(static_cast(std::ceil(std::numbers::pi_v / std::acos(1 - std::ranges::min(radius, max_error) / radius)))), + circle_segments_min, + circle_segments_max + ); + } + + [[nodiscard]] constexpr auto circle_segments_calc_radius(const std::size_t n, const float max_error) noexcept -> auto + { + return max_error / (1 - math::cos(std::numbers::pi_v / std::ranges::max(static_cast(n), std::numbers::pi_v))); + } + + [[nodiscard]] constexpr auto circle_segments_calc_error(const std::size_t n, const float radius) noexcept -> auto + { + return (1 - math::cos(std::numbers::pi_v / std::ranges::max(static_cast(n), std::numbers::pi_v))) / radius; + } + + template + [[nodiscard]] constexpr auto vertex_sample_points_calc() noexcept -> DrawListSharedData::vertex_sample_points_type + { + return [](std::index_sequence) noexcept -> DrawListSharedData::vertex_sample_points_type + { + constexpr auto make_point = []() noexcept -> DrawListSharedData::point_type + { + const auto a = static_cast(I) / static_cast(N) * 2 * std::numbers::pi_v; + return {math::cos(a), -math::sin(a)}; + }; + + return {{make_point.template operator()()...}}; + }(std::make_index_sequence{}); + } +} + +namespace gal::prometheus::draw +{ + [[nodiscard]] auto range_of_arc_quadrant(const DrawArcFlag quadrant) noexcept -> std::pair + { + static_assert(DrawListSharedData::vertex_sample_points_count % 12 == 0); + constexpr auto factor = static_cast(DrawListSharedData::vertex_sample_points_count / 12); + + switch (quadrant) + { + case DrawArcFlag::Q1: { return std::make_pair(0 * factor, 3 * factor); } + case DrawArcFlag::Q2: { return std::make_pair(3 * factor, 6 * factor); } + case DrawArcFlag::Q3: { return std::make_pair(6 * factor, 9 * factor); } + case DrawArcFlag::Q4: { return std::make_pair(9 * factor, 12 * factor); } + case DrawArcFlag::TOP: { return std::make_pair(0 * factor, 6 * factor); } + case DrawArcFlag::BOTTOM: { return std::make_pair(6 * factor, 12 * factor); } + case DrawArcFlag::LEFT: { return std::make_pair(3 * factor, 9 * factor); } + case DrawArcFlag::RIGHT: { return std::make_pair(9 * factor, 15 * factor); } + case DrawArcFlag::ALL: { return std::make_pair(0 * factor, 12 * factor); } + case DrawArcFlag::Q1_CLOCK_WISH: { return std::make_pair(3 * factor, 0 * factor); } + case DrawArcFlag::Q2_CLOCK_WISH: { return std::make_pair(6 * factor, 3 * factor); } + case DrawArcFlag::Q3_CLOCK_WISH: { return std::make_pair(9 * factor, 6 * factor); } + case DrawArcFlag::Q4_CLOCK_WISH: { return std::make_pair(12 * factor, 9 * factor); } + case DrawArcFlag::TOP_CLOCK_WISH: { return std::make_pair(6 * factor, 0 * factor); } + case DrawArcFlag::BOTTOM_CLOCK_WISH: { return std::make_pair(12 * factor, 6 * factor); } + case DrawArcFlag::LEFT_CLOCK_WISH: { return std::make_pair(9 * factor, 3 * factor); } + case DrawArcFlag::RIGHT_CLOCK_WISH: { return std::make_pair(15 * factor, 9 * factor); } + case DrawArcFlag::ALL_CLOCK_WISH: { return std::make_pair(12 * factor, 0 * factor); } + default: { GAL_PROMETHEUS_ERROR_UNREACHABLE(); } + } + } + + DrawListSharedData::DrawListSharedData() noexcept + : + circle_segment_counts_{}, + vertex_sample_points_{vertex_sample_points_calc()}, + circle_segment_max_error_{}, + arc_fast_radius_cutoff_{}, + curve_tessellation_tolerance_{1.25f} + { + set_circle_tessellation_max_error(.3f); + } + + auto DrawListSharedData::get_circle_auto_segment_count(const float radius) const noexcept -> circle_segment_count_type + { + // ceil to never reduce accuracy + if (const auto radius_index = static_cast(radius + .999999f); + radius_index < circle_segment_counts_.size()) + { + return circle_segment_counts_[radius_index]; + } + return static_cast(circle_segments_calc(radius, circle_segment_max_error_)); + } + + auto DrawListSharedData::get_vertex_sample_point(const std::size_t index) const noexcept -> const point_type& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < vertex_sample_points_.size()); + + return vertex_sample_points_[index]; + } + + auto DrawListSharedData::get_circle_tessellation_max_error() const noexcept -> float + { + return circle_segment_max_error_; + } + + auto DrawListSharedData::get_arc_fast_radius_cutoff() const noexcept -> float + { + return arc_fast_radius_cutoff_; + } + + auto DrawListSharedData::get_curve_tessellation_tolerance() const noexcept -> float + { + return curve_tessellation_tolerance_; + } + + auto DrawListSharedData::set_circle_tessellation_max_error(const float max_error) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(max_error > .0f); + + if (circle_segment_max_error_ == max_error) // NOLINT(clang-diagnostic-float-equal) + { + return; + } + + for (decltype(circle_segment_counts_.size()) i = 0; i < circle_segment_counts_.size(); ++i) + { + const auto radius = static_cast(i); + circle_segment_counts_[i] = static_cast(circle_segments_calc(radius, max_error)); + } + circle_segment_max_error_ = max_error; + arc_fast_radius_cutoff_ = circle_segments_calc_radius(vertex_sample_points_count, max_error); + } + + auto DrawListSharedData::set_curve_tessellation_tolerance(const float tolerance) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(tolerance > .0f); + + curve_tessellation_tolerance_ = tolerance; + } +} diff --git a/src/draw/shared_data.hpp b/src/draw/shared_data.hpp new file mode 100644 index 00000000..8fbd8668 --- /dev/null +++ b/src/draw/shared_data.hpp @@ -0,0 +1,63 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +namespace gal::prometheus::draw +{ + class DrawListSharedData final + { + public: + using rect_type = DrawListDef::rect_type; + using point_type = DrawListDef::point_type; + using extent_type = DrawListDef::extent_type; + + using circle_segment_count_type = std::uint8_t; + constexpr static std::size_t circle_segment_counts_count = 64; + using circle_segment_counts_type = std::array; + + constexpr static std::uint32_t circle_segments_min = 4; + constexpr static std::uint32_t circle_segments_max = 512; + + constexpr static std::size_t vertex_sample_points_count = 48; + using vertex_sample_points_type = std::array; + + private: + circle_segment_counts_type circle_segment_counts_; + vertex_sample_points_type vertex_sample_points_; + + // Maximum error (in pixels) allowed when using `circle`/`circle_filled` or drawing rounded corner rectangles with no explicit segment count specified. + // Decrease for higher quality but more geometry. + float circle_segment_max_error_; + // Cutoff radius after which arc drawing will fall back to slower `path_arc` + float arc_fast_radius_cutoff_; + // Tessellation tolerance when using `path_bezier_curve` without a specific number of segments. + // Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + float curve_tessellation_tolerance_; + + public: + DrawListSharedData() noexcept; + + // -------------------------------------------------- + + [[nodiscard]] auto get_circle_auto_segment_count(float radius) const noexcept -> circle_segment_count_type; + + [[nodiscard]] auto get_vertex_sample_point(std::size_t index) const noexcept -> const point_type&; + + [[nodiscard]] auto get_circle_tessellation_max_error() const noexcept -> float; + + [[nodiscard]] auto get_arc_fast_radius_cutoff() const noexcept -> float; + + [[nodiscard]] auto get_curve_tessellation_tolerance() const noexcept -> float; + + // -------------------------------------------------- + + auto set_circle_tessellation_max_error(float max_error) noexcept -> void; + + auto set_curve_tessellation_tolerance(float tolerance) noexcept -> void; + }; +} diff --git a/src/draw/theme.cpp b/src/draw/theme.cpp new file mode 100644 index 00000000..ccd36c03 --- /dev/null +++ b/src/draw/theme.cpp @@ -0,0 +1,126 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. +#include + +namespace +{ + using namespace gal::prometheus; + using colors_type = draw::Theme::colors_type; + + [[nodiscard]] constexpr auto default_colors() noexcept -> colors_type + { + using draw::ThemeCategory; + + colors_type colors{}; + + colors[static_cast(ThemeCategory::TEXT)] = primitive::colors::black; + colors[static_cast(ThemeCategory::BORDER)] = primitive::colors::magenta; + + colors[static_cast(ThemeCategory::WINDOW_BACKGROUND)] = primitive::colors::gains_boro; + + colors[static_cast(ThemeCategory::WIDGET_BACKGROUND)] = primitive::colors::white; + colors[static_cast(ThemeCategory::WIDGET_ACTIVATED)] = primitive::colors::dark_salmon; + + colors[static_cast(ThemeCategory::TITLE_BAR)] = primitive::colors::light_coral; + colors[static_cast(ThemeCategory::TITLE_BAR_COLLAPSED)] = primitive::colors::dark_khaki; + + colors[static_cast(ThemeCategory::SLIDER)] = primitive::colors::light_blue; + colors[static_cast(ThemeCategory::SLIDER_ACTIVATED)] = primitive::colors::deep_sky_blue; + + colors[static_cast(ThemeCategory::BUTTON)] = primitive::colors::sienna; + colors[static_cast(ThemeCategory::BUTTON_HOVERED)] = primitive::colors::slate_gray; + colors[static_cast(ThemeCategory::BUTTON_ACTIVATED)] = primitive::colors::steel_blue; + + colors[static_cast(ThemeCategory::RESIZE_GRIP)] = primitive::colors::gold; + colors[static_cast(ThemeCategory::RESIZE_GRIP_HOVERED)] = primitive::colors::peru; + colors[static_cast(ThemeCategory::RESIZE_GRIP_ACTIVATED)] = primitive::colors::powder_blue; + + colors[static_cast(ThemeCategory::TOOLTIP_BACKGROUND)] = primitive::colors::black; + colors[static_cast(ThemeCategory::TOOLTIP_TEXT)] = primitive::colors::red; + + return colors; + } + + [[nodiscard]] constexpr auto another_colors_for_test() noexcept -> colors_type + { + using draw::ThemeCategory; + + colors_type colors{}; + + colors[static_cast(ThemeCategory::TEXT)] = primitive::colors::black; + colors[static_cast(ThemeCategory::BORDER)] = primitive::colors::magenta; + + colors[static_cast(ThemeCategory::WINDOW_BACKGROUND)] = primitive::colors::pink; + + colors[static_cast(ThemeCategory::WIDGET_BACKGROUND)] = primitive::colors::white; + colors[static_cast(ThemeCategory::WIDGET_ACTIVATED)] = primitive::colors::dark_salmon; + + colors[static_cast(ThemeCategory::TITLE_BAR)] = primitive::colors::light_coral; + colors[static_cast(ThemeCategory::TITLE_BAR_COLLAPSED)] = primitive::colors::dark_khaki; + + colors[static_cast(ThemeCategory::SLIDER)] = primitive::colors::light_blue; + colors[static_cast(ThemeCategory::SLIDER_ACTIVATED)] = primitive::colors::deep_sky_blue; + + colors[static_cast(ThemeCategory::BUTTON)] = primitive::colors::sienna; + colors[static_cast(ThemeCategory::BUTTON_HOVERED)] = primitive::colors::slate_gray; + colors[static_cast(ThemeCategory::BUTTON_ACTIVATED)] = primitive::colors::steel_blue; + + colors[static_cast(ThemeCategory::RESIZE_GRIP)] = primitive::colors::red; + colors[static_cast(ThemeCategory::RESIZE_GRIP_HOVERED)] = primitive::colors::yellow; + colors[static_cast(ThemeCategory::RESIZE_GRIP_ACTIVATED)] = primitive::colors::blue; + + colors[static_cast(ThemeCategory::TOOLTIP_BACKGROUND)] = primitive::colors::black; + colors[static_cast(ThemeCategory::TOOLTIP_TEXT)] = primitive::colors::blue; + + return colors; + } +} + +namespace gal::prometheus::draw +{ + auto Theme::default_theme() noexcept -> Theme + { + return + { + #if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) + .font_path = R"(C:\Windows\Fonts\msyh.ttc)", + .font_size = 18, + #else + #error "fixme" + #endif + .title_bar_height = 20, + .window_rounding = 0, + .window_padding = {8, 8}, + .window_min_size = {640, 480}, + .resize_grip_size = {20, 20}, + .frame_padding = {4, 4}, + .item_spacing = {10, 5}, + .item_inner_spacing = {5, 5}, + .colors = default_colors() + }; + } + + auto Theme::another_theme_for_test() noexcept -> Theme + { + return + { + #if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) + .font_path = R"(C:\Windows\Fonts\msyh.ttc)", + .font_size = 24, + #else + #error "fixme" + #endif + .title_bar_height = 40, + .window_rounding = 5, + .window_padding = {15, 15}, + .window_min_size = {1280, 960}, + .resize_grip_size = {40, 40}, + .frame_padding = {8, 8}, + .item_spacing = {20, 10}, + .item_inner_spacing = {10, 10}, + .colors = another_colors_for_test() + }; + } +} diff --git a/src/draw/theme.hpp b/src/draw/theme.hpp new file mode 100644 index 00000000..e3a71bb5 --- /dev/null +++ b/src/draw/theme.hpp @@ -0,0 +1,63 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include + +namespace gal::prometheus::draw +{ + class [[nodiscard]] Theme final + { + public: + using rect_type = DrawListDef::rect_type; + using point_type = DrawListDef::point_type; + using extent_type = DrawListDef::extent_type; + + using value_type = point_type::value_type; + + using color_type = DrawListDef::color_type; + using colors_type = std::array; + + // ----------------------------------------------- + // FONT + + std::string font_path; + value_type font_size; + + // ----------------------------------------------- + // WINDOW + + value_type title_bar_height; + value_type window_rounding; + extent_type window_padding; + extent_type window_min_size; + extent_type resize_grip_size; + + // ----------------------------------------------- + // WINDOW CANVAS LAYOUT + + extent_type frame_padding; + extent_type item_spacing; + extent_type item_inner_spacing; + + // ----------------------------------------------- + // WINDOW WIDGET COLOR + + colors_type colors; + + template + requires (std::to_underlying(Category) < theme_category_count) + [[nodiscard]] constexpr auto color() const noexcept -> color_type + { + return colors[static_cast(Category)]; + } + + [[nodiscard]] static auto default_theme() noexcept -> Theme; + + [[nodiscard]] static auto another_theme_for_test() noexcept -> Theme; + }; +} diff --git a/src/draw/window.cpp b/src/draw/window.cpp new file mode 100644 index 00000000..8bfdb794 --- /dev/null +++ b/src/draw/window.cpp @@ -0,0 +1,648 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. +#include + +#include + +namespace +{ + using namespace gal::prometheus; + + constexpr std::string_view window_widget_name_move{"@WINDOW::MOVE@"}; + constexpr std::string_view window_widget_name_close{"@WINDOW::CLOSE@"}; + constexpr std::string_view window_widget_name_resize{"@WINDOW::RESIZE@"}; +} + +namespace gal::prometheus::draw +{ + auto Window::get_id(const std::string_view name) const noexcept -> id_type + { + return functional::hash_combine_2(id_, functional::hash(name)); + } + + auto Window::rect_of_title_bar() const noexcept -> rect_type + { + const auto& context = Context::instance(); + const auto& theme = context.theme(); + + const auto point = rect_.left_top(); + const auto size = extent_type{rect_.width(), theme.title_bar_height}; + return {point, size}; + } + + auto Window::rect_of_close_button() const noexcept -> rect_type + { + const auto& context = Context::instance(); + const auto& theme = context.theme(); + + // RIGHT-TOP-CORNER + const auto point = rect_.right_top() - extent_type{theme.title_bar_height, 0}; + const auto size = extent_type{theme.title_bar_height, theme.title_bar_height}; + return {point, size}; + } + + auto Window::rect_of_resize_grip() const noexcept -> rect_type + { + const auto& context = Context::instance(); + const auto& theme = context.theme(); + + // RIGHT-BOTTOM-CORNER + const auto point = rect_.right_bottom() - theme.resize_grip_size; + const auto size = theme.resize_grip_size; + return {point, size}; + } + + auto Window::rect_of_canvas() const noexcept -> rect_type + { + // const auto& context = Context::instance(); + // const auto& theme = context.theme(); + + return rect_; + } + + auto Window::make_canvas() noexcept -> bool + { + auto& window = *this; + + auto& canvas = window.canvas_; + auto& draw_list = window.draw_list_; + const auto flag_value = std::to_underlying(window.flag_); + + auto& context = Context::instance(); + const auto& theme = context.theme(); + const auto& mouse = context.mouse(); + + // ------------------------------ + // TEST TITLE BAR + auto close_button_color = theme.color(); + auto close_button_pressed = false; + { + if (flag_value & std::to_underlying(WindowFlag::NO_TITLE_BAR)) + { + window.collapse_ = false; + } + else if (mouse.double_clicked() and window.rect_of_title_bar().includes(mouse.position())) + { + window.collapse_ = not window.collapse_; + } + + if (flag_value & std::to_underlying(WindowFlag::NO_CLOSE)) + { + // + } + else + { + const auto id = get_id(window_widget_name_close); + const auto close_button_rect = window.rect_of_close_button(); + + const auto status = context.test_widget_status( + id, + close_button_rect, + false + #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) + , + std::format("Test Window({})'s close-button({}).", window.name_, close_button_rect) + #endif + ); + + if (status.hovered) + { + if (status.keeping) + { + close_button_color = theme.color(); + } + else + { + close_button_color = theme.color(); + } + } + + if (status.pressed) + { + close_button_pressed = true; + } + } + + if (flag_value & std::to_underlying(WindowFlag::NO_MOVE)) + { + // + } + else + { + if (const auto id = get_id(window_widget_name_move); + context.is_widget_activated(id)) + { + if (mouse.down()) + { + window.rect_.point += mouse.position_delta(); + } + else + { + context.invalidate_widget_activated( + #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) + std::format("{} is not a movable window.", window.name_) + #endif + ); + } + } + } + } + + // ------------------------------ + // TEST RESIZE GRIP + auto resize_grip_color = theme.color(); + { + if (flag_value & std::to_underlying(WindowFlag::NO_RESIZE)) + { + // + } + else + { + if (not window.collapse_) + { + const auto id = get_id(window_widget_name_resize); + const auto resize_grip_rect = window.rect_of_resize_grip(); + + const auto status = context.test_widget_status( + id, + resize_grip_rect, + false + #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) + , + std::format("Test Window({})'s resize-grip({}).", window.name_, resize_grip_rect) + #endif + ); + + if (status.keeping) + { + const auto target_size = window.rect_.size() + mouse.position_delta(); + const auto min_size = theme.window_min_size; + window.rect_.extent = {std::ranges::max(target_size.width, min_size.width), std::ranges::max(target_size.height, min_size.height)}; + resize_grip_color = theme.color(); + } + else if (status.hovered) + { + resize_grip_color = theme.color(); + } + } + } + } + + // ------------------------------ + // INIT CANVAS + { + if (flag_value & std::to_underlying(WindowFlag::NO_TITLE_BAR)) + { + canvas.cursor_start_line = (theme.window_padding + extent_type{0, 0}).to(); + } + else + { + canvas.cursor_start_line = (theme.window_padding + extent_type{0, theme.title_bar_height}).to(); + } + canvas.cursor_current_line = canvas.cursor_start_line; + canvas.cursor_previous_line = canvas.cursor_current_line; + canvas.height_current_line = 0; + canvas.height_previous_line = 0; + canvas.item_width.resize(0); + canvas.item_width.push_back(window.default_item_width_); + } + + // ------------------------------ + // DRAW CANVAS BACKGROUND + { + const auto canvas_rect = window.rect_of_canvas(); + + if (window.collapse_) + { + // + } + else + { + draw_list.rect_filled(canvas_rect, theme.color(), theme.window_rounding, DrawFlag::ROUND_CORNER_ALL); + if (flag_value & std::to_underlying(WindowFlag::BORDERED)) + { + draw_list.rect(canvas_rect, theme.color(), theme.window_rounding, DrawFlag::ROUND_CORNER_ALL); + } + } + } + + // ------------------------------ + // DRAW TITTLE BAR + { + const auto title_bar_rect = window.rect_of_title_bar(); + + if (window.collapse_) + { + draw_list.rect_filled(title_bar_rect, theme.color(), theme.window_rounding, DrawFlag::ROUND_CORNER_ALL); + + if (flag_value & std::to_underlying(WindowFlag::BORDERED)) + { + draw_list.rect(title_bar_rect, theme.color(), theme.window_rounding, DrawFlag::ROUND_CORNER_ALL); + } + } + else + { + draw_list.rect_filled(title_bar_rect, theme.color(), theme.window_rounding, DrawFlag::ROUND_CORNER_TOP); + } + + // todo: position + const auto text_point = title_bar_rect.left_top() + extent_type{theme.item_inner_spacing.width, 0}; + draw_list.text( + theme.font_size, + text_point, + theme.color(), + window.name_ + ); + + const auto close_button_rect = window.rect_of_close_button(); + if (flag_value & std::to_underlying(WindowFlag::NO_CLOSE)) + { + // + } + else + { + const auto center = close_button_rect.center(); + const auto radius = close_button_rect.width() / 2; + + const auto r = radius / std::numbers::sqrt2_v; + + const auto line1_from = center + extent_type{-r, -r}; + const auto line1_to = center + extent_type{r, r}; + + const auto line2_from = center + extent_type{-r, r}; + const auto line2_to = center + extent_type{r, -r}; + + draw_list.circle_filled(close_button_rect.center(), close_button_rect.width() / 2, close_button_color); + draw_list.line(line1_from, line1_to, theme.color()); + draw_list.line(line2_from, line2_to, theme.color()); + } + } + + // ------------------------------ + // DRAW RESIZE GRIP + { + const auto resize_grip_rect = window.rect_of_resize_grip(); + + if (window.collapse_ or (flag_value & std::to_underlying(WindowFlag::NO_RESIZE))) + { + // + } + else + { + // todo: rounding? + draw_list.triangle_filled(resize_grip_rect.left_bottom(), resize_grip_rect.right_bottom(), resize_grip_rect.right_top(), resize_grip_color); + } + } + + return close_button_pressed; + } + + auto Window::cursor_abs_position() const noexcept -> point_type + { + auto& window = *this; + + const auto& canvas = window.canvas_; + // auto& draw_list = window.draw_list_; + // const auto flag_value = std::to_underlying(window.flag_); + + // const auto& context = Context::instance(); + // const auto& theme = context.theme(); + // const auto& mouse = context.mouse(); + + return window.rect_.left_top() + canvas.cursor_current_line; + } + + auto Window::cursor_remaining_width() const noexcept -> value_type + { + auto& window = *this; + + const auto& canvas = window.canvas_; + // auto& draw_list = window.draw_list_; + // const auto flag_value = std::to_underlying(window.flag_); + + // const auto& context = Context::instance(); + // const auto& theme = context.theme(); + // const auto& mouse = context.mouse(); + + return window.rect_.width() - canvas.cursor_current_line.x; + } + + auto Window::adjust_item_size(const extent_type& size) noexcept -> void + { + auto& window = *this; + + auto& canvas = window.canvas_; + // auto& draw_list = window.draw_list_; + // const auto flag_value = std::to_underlying(window.flag_); + + const auto& context = Context::instance(); + const auto& theme = context.theme(); + // const auto& mouse = context.mouse(); + + if (window.collapse_) + { + return; + } + + const auto line_height = std::ranges::max(canvas.height_current_line, size.height); + + // Always align ourselves on pixel boundaries + canvas.cursor_previous_line = {canvas.cursor_current_line.x + size.width, canvas.cursor_current_line.y}; + canvas.cursor_current_line = {theme.window_padding.width, canvas.cursor_current_line.y + line_height + theme.item_spacing.height}; + + canvas.height_previous_line = line_height; + canvas.height_current_line = 0; + } + + auto Window::draw_widget_frame(const rect_type& rect, const color_type& color) noexcept -> void + { + auto& window = *this; + + // auto& canvas = window.canvas_; + auto& draw_list = window.draw_list_; + const auto flag_value = std::to_underlying(window.flag_); + + const auto& context = Context::instance(); + // const auto& font = context.font(); + const auto& theme = context.theme(); + // const auto& mouse = context.mouse(); + + draw_list.rect_filled(rect, color); + if (flag_value & std::to_underlying(WindowFlag::BORDERED)) + { + draw_list.rect(rect, theme.color()); + } + } + + Window::Window(const std::string_view name, const WindowFlag flag, const rect_type& rect) noexcept + : name_{name}, + id_{functional::hash(name_)}, + flag_{flag}, + rect_{rect}, + default_item_width_{0}, + visible_{true}, + collapse_{false} {} + + auto Window::make(const std::string_view name, const WindowFlag flag, const rect_type& rect) noexcept -> Window + { + Window window{name, flag, rect}; + window.draw_list_.reset(); + // todo + std::ignore = window.make_canvas(); + + return window; + } + + auto Window::name() const noexcept -> std::string_view + { + return name_; + } + + auto Window::rect() const noexcept -> const rect_type& + { + return rect_; + } + + auto Window::hovered(const point_type mouse) const noexcept -> bool + { + // todo + return rect_.includes(mouse); + } + + auto Window::layout_same_line(const value_type column_width, value_type spacing_width) noexcept -> void + { + auto& window = *this; + + auto& canvas = window.canvas_; + // auto& draw_list = window.draw_list_; + // const auto flag_value = std::to_underlying(window.flag_); + + const auto& context = Context::instance(); + // const auto& font = context.font(); + const auto& theme = context.theme(); + // const auto& mouse = context.mouse(); + + if (window.collapse_) + { + return; + } + + canvas.height_current_line = canvas.height_previous_line; + canvas.cursor_current_line = canvas.cursor_previous_line; + + if (column_width <= 0) + { + if (spacing_width <= 0) + { + spacing_width = theme.item_spacing.width; + } + + canvas.cursor_current_line.x += spacing_width; + } + else + { + spacing_width = std::ranges::max(spacing_width, static_cast(0)); + + canvas.cursor_current_line.x = column_width + spacing_width; + } + } + + auto Window::draw_text(const std::string_view utf8_text) noexcept -> void + { + auto& window = *this; + + // const auto& canvas = window.canvas_; + auto& draw_list = window.draw_list_; + // const auto flag_value = std::to_underlying(window.flag_); + + const auto& context = Context::instance(); + const auto& font = context.font(); + const auto& theme = context.theme(); + // const auto& mouse = context.mouse(); + + if (window.collapse_) + { + return; + } + + const auto text_context_size = font.text_size(utf8_text, theme.font_size, cursor_remaining_width()); + + const auto text_point = cursor_abs_position(); + const auto text_size = text_context_size; + const rect_type text_rect{text_point, text_size}; + adjust_item_size(text_size); + + draw_list.text( + font, + theme.font_size, + text_rect.left_top(), + theme.color(), + utf8_text, + text_rect.width() + ); + } + + auto Window::draw_button(std::string_view utf8_text, extent_type size) noexcept -> bool + { + auto& window = *this; + + // const auto& canvas = window.canvas_; + auto& draw_list = window.draw_list_; + // const auto flag_value = std::to_underlying(window.flag_); + + auto& context = Context::instance(); + const auto& font = context.font(); + const auto& theme = context.theme(); + // const auto& mouse = context.mouse(); + + if (window.collapse_) + { + return false; + } + + const auto text_context_size = font.text_size(utf8_text, theme.font_size, cursor_remaining_width()); + + if (size.width <= 0) + { + size.width = text_context_size.width; + } + if (size.height <= 0) + { + size.height = text_context_size.height; + } + + // todo + const auto text_point = cursor_abs_position() + extent_type{theme.item_inner_spacing.width, theme.frame_padding.height}; + + const auto button_point = cursor_abs_position(); + const auto button_size = size + theme.frame_padding * 2; + const rect_type button_rect{button_point, button_size}; + adjust_item_size(button_size); + + const auto id = get_id(utf8_text); + const auto status = context.test_widget_status( + id, + button_rect, + false + #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) + , + std::format("Test Window({})'s button[{}]({}).", window.name_, utf8_text, button_rect) + #endif + ); + + color_type button_color = theme.color(); + { + if (status.keeping or status.pressed) + { + button_color = theme.color(); + } + else if (status.hovered) + { + button_color = theme.color(); + } + } + draw_widget_frame(button_rect, button_color); + + draw_list.text( + font, + theme.font_size, + text_point, + theme.color(), + utf8_text, + button_rect.width() + ); + + return status.pressed; + } + + auto Window::draw_checkbox(std::string_view utf8_text, bool checked, extent_type size) noexcept -> bool + { + auto& window = *this; + + // const auto& canvas = window.canvas_; + auto& draw_list = window.draw_list_; + // const auto flag_value = std::to_underlying(window.flag_); + + auto& context = Context::instance(); + const auto& font = context.font(); + const auto& theme = context.theme(); + // const auto& mouse = context.mouse(); + + if (window.collapse_) + { + return false; + } + + const auto text_context_size = font.text_size(utf8_text, theme.font_size, cursor_remaining_width()); + + if (size.width <= 0) + { + size.width = text_context_size.width; + } + if (size.height <= 0) + { + size.height = text_context_size.height; + } + + // □ + text + + // □, side length equals string rect height + const auto check_point = cursor_abs_position(); + const auto check_size = extent_type{size.height + theme.frame_padding.height * 2, size.height + theme.frame_padding.height * 2}; + const rect_type check_rect{check_point, check_size}; + adjust_item_size(check_size); + + // □ text + layout_same_line(0, theme.item_inner_spacing.width); + + // text + const auto text_point = cursor_abs_position() + extent_type{0, theme.frame_padding.height}; + const auto text_size = size; + const rect_type text_rect{text_point, text_size}; + adjust_item_size(text_size); + + draw_widget_frame(check_rect, theme.color()); + + const auto id = get_id(utf8_text); + const auto status = context.test_widget_status( + id, + check_rect, + false + #if defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) + , + std::format("Test Window({})'s checkbox[{}]({}).", window.name_, utf8_text, check_rect) + #endif + ); + + if (status.pressed) + { + checked = not checked; + } + + if (checked) + { + const auto check_fill_point = check_point + theme.item_inner_spacing; + const auto check_fill_size = check_size - theme.item_inner_spacing * 2; + const rect_type check_fill_rect{check_fill_point, check_fill_size}; + draw_list.rect_filled(check_fill_rect, theme.color()); + } + + draw_list.text( + font, + theme.font_size, + text_rect.left_top(), + theme.color(), + utf8_text, + text_rect.width() + ); + + return checked; + } + + auto Window::render() noexcept -> DrawList& + { + return draw_list_; + } +} diff --git a/src/draw/window.hpp b/src/draw/window.hpp new file mode 100644 index 00000000..07f32223 --- /dev/null +++ b/src/draw/window.hpp @@ -0,0 +1,151 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include +#include +#include +#include + +namespace gal::prometheus::draw +{ + class [[nodiscard]] Window final + { + public: + template + using container_type = DrawListDef::container_type; + + using rect_type = DrawListDef::rect_type; + using point_type = DrawListDef::point_type; + using extent_type = DrawListDef::extent_type; + + using value_type = point_type::value_type; + + using color_type = DrawListDef::color_type; + + using id_type = functional::hash_result_type; + constexpr static auto invalid_id = std::numeric_limits::max(); + + private: + struct canvas_type + { + point_type cursor_start_line{0}; + point_type cursor_current_line{0}; + point_type cursor_previous_line{0}; + + value_type height_current_line{0}; + value_type height_previous_line{0}; + + container_type item_width{}; + }; + + std::string name_; + id_type id_; + WindowFlag flag_; + rect_type rect_; + + DrawList draw_list_; + + canvas_type canvas_; + + value_type default_item_width_; + + bool visible_; + bool collapse_; + + // ----------------------------------- + // ID + + [[nodiscard]] auto get_id(std::string_view name) const noexcept -> id_type; + + // ----------------------------------- + // CANVAS + + [[nodiscard]] auto rect_of_title_bar() const noexcept -> rect_type; + + [[nodiscard]] auto rect_of_close_button() const noexcept -> rect_type; + + [[nodiscard]] auto rect_of_resize_grip() const noexcept -> rect_type; + + [[nodiscard]] auto rect_of_canvas() const noexcept -> rect_type; + + [[nodiscard]] auto make_canvas() noexcept -> bool; + + // ----------------------------------- + // CANVAS CONTEXT + + [[nodiscard]] auto cursor_abs_position() const noexcept -> point_type; + + [[nodiscard]] auto cursor_remaining_width() const noexcept -> value_type; + + auto adjust_item_size(const extent_type& size) noexcept -> void; + + auto draw_widget_frame(const rect_type& rect, const color_type& color) noexcept -> void; + + // ----------------------------------- + // INITIALIZE + + Window(std::string_view name, WindowFlag flag, const rect_type& rect) noexcept; + + public: + static auto make(std::string_view name, WindowFlag flag, const rect_type& rect) noexcept -> Window; + + // --------------------------------------------- + // INFO + + [[nodiscard]] auto name() const noexcept -> std::string_view; + + [[nodiscard]] auto rect() const noexcept -> const rect_type&; + + // --------------------------------------------- + // STATUS + + [[nodiscard]] auto hovered(point_type mouse) const noexcept -> bool; + + // --------------------------------------------- + // LAYOUT + + auto layout_same_line(value_type column_width = 0, value_type spacing_width = 0) noexcept -> void; + + // --------------------------------------------- + // WIDGET + + auto draw_text(std::string_view utf8_text) noexcept -> void; + + /** + * @return Whether the button is pressed or not. + */ + [[nodiscard]] auto draw_button(std::string_view utf8_text, extent_type size = {0, 0}) noexcept -> bool; + + /** + * @return Whether the checkbox state is toggled (checked to unchecked, or vice versa) + */ + [[nodiscard]] auto draw_checkbox(std::string_view utf8_text, bool checked, extent_type size = {0, 0}) noexcept -> bool; + + // --------------------------------------------- + // RENDER + + auto render() noexcept -> DrawList&; + + // --------------------------------------------- + // for test only + + Window(const std::string_view name, const rect_type& rect) noexcept + : Window{name, WindowFlag::NONE, rect} {} + + auto test_init() noexcept -> void + { + std::ignore = make_canvas(); + } + + [[nodiscard]] auto test_draw_list() noexcept -> DrawList& + { + return draw_list_; + } + }; +} diff --git a/src/error/command_line.impl.ixx b/src/error/command_line.impl.ixx deleted file mode 100644 index ba9ec2a6..00000000 --- a/src/error/command_line.impl.ixx +++ /dev/null @@ -1,58 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include -#if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) -#include -#endif - -export module gal.prometheus.error:command_line.impl; - -import std; -import :command_line; - -#else - -#if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) -#include -#endif - -#include -#include - -#endif - -namespace -{ - #if defined(GAL_PROMETHEUS_COMPILER_MSVC) - const int g_argc = *__p___argc(); - const char* const* g_argv = *__p___argv(); - #else - int g_argc = 0; - const char** g_argv = nullptr; - - __attribute__((constructor)) auto do_read_command_line(const int argc, const char* argv[]) -> void - { - g_argc = argc; - g_argv = argv; - } - #endif -} - -namespace gal::prometheus::error -{ - auto command_line_args_count() noexcept -> int - { - return g_argc; - } - - auto command_line_args() noexcept -> std::span - { - return {g_argv, static_cast::size_type>(command_line_args_count())}; - } -} diff --git a/src/error/command_line.ixx b/src/error/command_line.ixx deleted file mode 100644 index 731016d9..00000000 --- a/src/error/command_line.ixx +++ /dev/null @@ -1,29 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.error:command_line; - -import std; - -#else -#pragma once - -#include - -#include - -#endif - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::error) -{ - auto command_line_args_count() noexcept -> int; - - auto command_line_args() noexcept -> std::span; -} // namespace gal::prometheus::error diff --git a/src/error/debug.impl.ixx b/src/error/debug.impl.ixx deleted file mode 100644 index d5348a8d..00000000 --- a/src/error/debug.impl.ixx +++ /dev/null @@ -1,124 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include -#if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) -#include -#endif - -export module gal.prometheus.error:debug.impl; - -import std; -import :debug; - -#else -#if __has_include() -#include -#endif -#include -#include -#include - -#if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) -#include -#else -#include -#include -#endif - -#include -#include - -#endif - -namespace -{ - auto call_debugger() noexcept -> bool - { - #if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) - if (IsDebuggerPresent()) - { - // When running under the debugger, __debugbreak() after returning. - return true; - } - - __try // NOLINT(clang-diagnostic-language-extension-token) - { - __try // NOLINT(clang-diagnostic-language-extension-token) - { - // Attempt to break, causing an exception. - DebugBreak(); - - // The UnhandledExceptionFilter() will be called to attempt to attach a debugger. - // * If the jit-debugger is not configured the user gets an error dialogue-box that - // with "Abort", "Retry (Debug)", "Ignore". The "Retry" option will only work - // when the application is already being debugged. - // * When the jit-debugger is configured the user gets a dialogue window which allows - // a selection of debuggers and an "OK (Debug)", "Cancel (aborts application)". - } - __except (UnhandledExceptionFilter(GetExceptionInformation())) - { - // The jit-debugger is not configured and the user pressed any of the buttons. - return false; - } - } - __except (EXCEPTION_EXECUTE_HANDLER) - { - // User pressed "OK", debugger has been attached, __debugbreak() after return. - return true; - } - - // The jit-debugger was configured, but the user pressed Cancel. - return false; - #else - // Check if we're running under a debugger by checking the process status. - static bool debugger_present = false; - static bool checked = false; - - if (not checked) { - debugger_present = (isatty(STDERR_FILENO) != 0); - checked = true; - } - - if (debugger_present) { - return true; - } - - // Raise a SIGTRAP signal to invoke the debugger. - raise(SIGTRAP); - - // After raising SIGTRAP, if a debugger is attached, the execution will stop. - // If no debugger is attached, the signal might be ignored, or the program may terminate. - // For simplicity, we assume that if we reach here without terminating, no debugger was attached. - return false; - #endif - } - - thread_local std::atomic terminate_reason{nullptr}; -} - -namespace gal::prometheus::error -{ - auto debug_break(const char* message) noexcept -> void - { - if (not call_debugger()) - { - #if __has_include() - std::println( - std::cerr, - "Unexpected behavior occurred but did not run under the debugger, terminate the program. \nReason. {}\n", - message - ); - #else - std::cerr << std::format("Unexpected behavior occurred but did not run under the debugger, terminate the program. \nReason. {}\n\n", message); - #endif - terminate_reason.store(message, std::memory_order_relaxed); - std::terminate(); - } - } -} diff --git a/src/error/debug.ixx b/src/error/debug.ixx deleted file mode 100644 index db0bfca4..00000000 --- a/src/error/debug.ixx +++ /dev/null @@ -1,23 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.error:debug; - -#else -#pragma once - -#include - -#endif - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::error) -{ - auto debug_break(const char* message) noexcept -> void; -} // namespace gal::prometheus::error diff --git a/src/error/error.ixx b/src/error/error.ixx deleted file mode 100644 index ea1b7a34..00000000 --- a/src/error/error.ixx +++ /dev/null @@ -1,24 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -export module gal.prometheus.error; - -export import :exception; -export import :debug; -export import :platform; -export import :command_line; -export import :instruction_set.ixx; - -#else -#pragma once - -#include -#include -#include -#include -#include - -#endif diff --git a/src/error/exception.ixx b/src/error/exception.ixx deleted file mode 100644 index 4db75dc2..00000000 --- a/src/error/exception.ixx +++ /dev/null @@ -1,239 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.error:exception; - -import std; - -#else -#pragma once - -#include -#include -#include - -#include -#endif - -// todo -#if __cpp_lib_stacktrace < 202011L -#warning "fixme: std::stacktrace" -namespace std -{ - struct stacktrace - { - [[nodiscard]] static auto current() -> stacktrace - { - return {}; - } - }; -} -#endif - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::error) -{ - class AbstractException - { - public: - constexpr AbstractException() noexcept = default; - constexpr AbstractException(const AbstractException&) noexcept = default; - constexpr AbstractException(AbstractException&&) noexcept = default; - constexpr auto operator=(const AbstractException&) noexcept -> AbstractException& = default; - constexpr auto operator=(AbstractException&&) noexcept -> AbstractException& = default; - constexpr virtual ~AbstractException() noexcept = default; - - [[nodiscard]] constexpr virtual auto what() const noexcept -> const std::string& = 0; - - [[nodiscard]] constexpr virtual auto where() const noexcept -> const std::source_location& = 0; - - [[nodiscard]] constexpr virtual auto when() const noexcept -> const std::stacktrace& = 0; - }; - - template - // ReSharper disable once CppClassCanBeFinal - class Exception : public AbstractException - { - public: - using data_type = T; - - private: - std::string message_; - std::source_location location_; - std::stacktrace stacktrace_; - data_type data_; - - public: - template - constexpr Exception( - StringType&& message, - DataType&& data, - const std::source_location location, - std::stacktrace&& stacktrace - ) noexcept - : AbstractException{}, - message_{std::forward(message)}, - location_{location}, - stacktrace_{std::move(stacktrace)}, - data_{std::forward(data)} {} - - [[nodiscard]] constexpr auto what() const noexcept -> const std::string& override { return message_; } - - [[nodiscard]] constexpr auto where() const noexcept -> const std::source_location& override { return location_; } - - [[nodiscard]] constexpr auto when() const noexcept -> const std::stacktrace& override { return stacktrace_; } - - [[nodiscard]] constexpr auto data() const noexcept -> data_type& { return data_; } - }; - - template<> - // ReSharper disable once CppClassCanBeFinal - class Exception : public AbstractException - { - public: - using data_type = void; - - private: - std::string message_; - std::source_location location_; - std::stacktrace stacktrace_; - - public: - template - constexpr Exception( - StringType&& message, - const std::source_location location, - std::stacktrace&& stacktrace - ) noexcept - : AbstractException{}, - message_{std::forward(message)}, - location_{location}, - stacktrace_{std::move(stacktrace)} {} - - [[nodiscard]] constexpr auto what() const noexcept -> const std::string& override { return message_; } - - [[nodiscard]] constexpr auto where() const noexcept -> const std::source_location& override { return location_; } - - [[nodiscard]] constexpr auto when() const noexcept -> const std::stacktrace& override { return stacktrace_; } - }; - - template - requires std::derived_from> - [[noreturn]] constexpr auto panic( - StringType&& message, - DataType&& data, - const std::source_location& location = std::source_location::current(), - std::stacktrace stacktrace = std::stacktrace::current()) noexcept(false) -> ExceptionType // - { - throw ExceptionType{ - std::forward(message), - std::forward(data), - location, - std::move(stacktrace) - }; - } - - template - requires std::derived_from> - [[noreturn]] constexpr auto panic( - StringType&& message, - const std::source_location& location = std::source_location::current(), - std::stacktrace stacktrace = std::stacktrace::current() - ) noexcept(false) -> ExceptionType // - { - throw ExceptionType{ - std::forward(message), - location, - std::move(stacktrace) - }; - } - - template - struct panic_selector; - - template<> - struct panic_selector - { - template - requires (std::derived_from> and std::is_same_v) - [[noreturn]] constexpr static auto invoke( - StringType&& message, - const std::source_location& location = std::source_location::current(), - std::stacktrace stacktrace = std::stacktrace::current() - ) noexcept(false) -> E - { - E::panic( - std::forward(message), - location, - std::move(stacktrace) - ); - GAL_PROMETHEUS_ERROR_UNREACHABLE(); - } - - template - requires (std::derived_from> and not std::is_same_v) - [[noreturn]] constexpr static auto invoke( - StringType&& message, - typename E::data_type&& data, - const std::source_location& location = std::source_location::current(), - std::stacktrace stacktrace = std::stacktrace::current() - ) noexcept(false) -> E - { - E::panic( - std::forward(message), - std::forward(data), - location, - std::move(stacktrace) - ); - GAL_PROMETHEUS_ERROR_UNREACHABLE(); - } - }; - - template<> - struct panic_selector - { - template - requires (std::derived_from> and std::is_same_v) - [[noreturn]] constexpr static auto invoke( - StringType&& message, - const std::source_location& location = std::source_location::current(), - std::stacktrace stacktrace = std::stacktrace::current() - ) noexcept(false) -> E - { - error::panic( - std::forward(message), - location, - std::move(stacktrace) - ); - } - - template - requires (std::derived_from> and not std::is_same_v) - [[noreturn]] constexpr static auto invoke( - StringType&& message, - typename E::data_type&& data, - const std::source_location& location = std::source_location::current(), - std::stacktrace stacktrace = std::stacktrace::current() - ) noexcept(false) -> E - { - error::panic( - std::forward(message), - std::forward(data), - location, - std::move(stacktrace) - ); - } - }; - - template - using mob = panic_selector< - requires { E::panic(std::declval()); } or - requires { E::panic(std::declval(), std::declval()); } - >; -} // namespace gal::prometheus::error diff --git a/src/error/platform.impl.ixx b/src/error/platform.impl.ixx deleted file mode 100644 index 84279a90..00000000 --- a/src/error/platform.impl.ixx +++ /dev/null @@ -1,45 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2023 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include -#if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) -#include -#else -#error "fixme" -#endif - -export module gal.prometheus.error:platform.impl; - -import std; -import :platform; - -#else -#include - -#include -#if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) -#include -#else -#include -#endif - -#endif - -namespace gal::prometheus::error -{ - auto get_error_message() -> std::string - { - return std::system_category().message( - #if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) - static_cast(GetLastError()) - #else - errno - #endif - ); - } -} // namespace gal::prometheus::error diff --git a/src/error/platform.ixx b/src/error/platform.ixx deleted file mode 100644 index 840d61e2..00000000 --- a/src/error/platform.ixx +++ /dev/null @@ -1,46 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE - -#include - -export module gal.prometheus.error:platform; - -import std; -import :exception; - -#else -#pragma once - -#include - -#include -#include - -#endif - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::error) -{ - /** - * @brief Get the OS error message from the last error received on this thread. - * @return A formatted message. - */ - [[nodiscard]] auto get_error_message() -> std::string; - - class OsError final : public Exception - { - public: - using Exception::Exception; - - [[noreturn]] static auto panic( - const std::source_location& location = std::source_location::current(), - std::stacktrace stacktrace = std::stacktrace::current() - ) noexcept(false) -> void // - { - error::panic(get_error_message(), location, std::move(stacktrace)); - } - }; -} // namespace gal::prometheus::error diff --git a/src/functional/aligned_union.ixx b/src/functional/aligned_union.hpp similarity index 83% rename from src/functional/aligned_union.ixx rename to src/functional/aligned_union.hpp index 9268415b..9304898c 100644 --- a/src/functional/aligned_union.ixx +++ b/src/functional/aligned_union.hpp @@ -1,21 +1,8 @@ // This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal +// Copyright (C) 2022-2025 Life4gal // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.functional:aligned_union; - -import std; - -import :type_list; -import :functor; - -#else #pragma once #include @@ -23,12 +10,11 @@ import :functor; #include #include -#include -#include -#endif +#include +#include -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::functional) +namespace gal::prometheus::functional { template class AlignedUnion final @@ -62,7 +48,7 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::functional) constexpr auto operator=(const AlignedUnion&) noexcept((std::is_nothrow_copy_assignable_v && ...)) -> AlignedUnion& // requires(std::is_copy_assignable_v && ...) = default; - // constexpr AlignedUnion(const AlignedUnion&) = delete; + // constexpr AlignedUnion(const AlignedUnion&) = delete; // constexpr auto operator=(const AlignedUnion&) -> AlignedUnion& = delete; constexpr AlignedUnion(AlignedUnion&&) noexcept((std::is_nothrow_move_constructible_v && ...)) // @@ -71,7 +57,7 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::functional) constexpr auto operator=(AlignedUnion&&) noexcept((std::is_nothrow_move_assignable_v && ...)) -> AlignedUnion& // requires(std::is_move_assignable_v && ...) = default; - // constexpr AlignedUnion(AlignedUnion&&) = delete; + // constexpr AlignedUnion(AlignedUnion&&) = delete; // constexpr auto operator=(AlignedUnion&&) -> AlignedUnion& = delete; constexpr ~AlignedUnion() noexcept = default; @@ -83,7 +69,9 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::functional) std::construct_at(reinterpret_cast(&data_), std::forward(args)...); } - // Note: If the type saved by AlignedUnion has a non-trivial destructor but no destroy is called, leaving AlignedUnion to destruct or calling store (if it already has a value) will be undefined behavior! (maybe memory or resource leak) + // Note: + // If the type saved by AlignedUnion has a non-trivial destructor but no destroy is called, + // leaving AlignedUnion to destruct or calling store (if it already has a value) will be undefined behavior! (maybe memory or resource leak) template requires(type_list.template any()) constexpr auto destroy() noexcept(std::is_nothrow_destructible_v) -> void { std::destroy_at(reinterpret_cast(&data_)); } @@ -93,7 +81,7 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::functional) constexpr auto exchange(Args&&... args) noexcept(std::is_nothrow_constructible_v and std::is_nothrow_move_constructible_v) -> Old { - auto&& old = std::move(this->template load()); + auto&& old = std::move(this->load()); this->template store(std::forward(args)...); return old; } @@ -102,7 +90,7 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::functional) requires(type_list.template any()) and (type_list.template any()) and std::is_constructible_v constexpr auto replace(Args&&... args) noexcept(std::is_nothrow_constructible_v and std::is_nothrow_destructible_v) -> void { - this->template destroy(); + this->destroy(); this->template store(std::forward(args)...); } @@ -136,7 +124,7 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::functional) template requires(type_list.template any()) [[nodiscard]] constexpr auto equal(const AlignedUnion& other) const - noexcept(noexcept(std::declval() == std::declval())) -> bool { return load() == other.template load(); } + noexcept(noexcept(std::declval() == std::declval())) -> bool { return load() == other.load(); } template requires(type_list.template any()) diff --git a/src/functional/enumeration.hpp b/src/functional/enumeration.hpp new file mode 100644 index 00000000..49ff4d23 --- /dev/null +++ b/src/functional/enumeration.hpp @@ -0,0 +1,216 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include + +#include + +// user_defined::enum_is_flag +#include + +// ============================================================================= +// operator| + +// flag | value => flag +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +[[nodiscard]] constexpr auto operator|(const EnumType lhs, const ValueType rhs) noexcept -> EnumType // + requires requires { std::to_underlying(lhs) | rhs; } +{ + return static_cast(std::to_underlying(lhs) | rhs); +} + +// value | flag => value +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +[[nodiscard]] constexpr auto operator|(const ValueType lhs, const EnumType rhs) noexcept -> ValueType // + requires requires { lhs | std::to_underlying(rhs); } +{ + return static_cast(lhs | std::to_underlying(rhs)); +} + +// flag | flag => flag +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +[[nodiscard]] constexpr auto operator|(const EnumType lhs, const EnumType rhs) noexcept -> EnumType // + requires requires { std::to_underlying(lhs) | std::to_underlying(rhs); } +{ + return static_cast(std::to_underlying(lhs) | std::to_underlying(rhs)); +} + +// flag |= value => flag +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +constexpr auto operator|=(EnumType& lhs, const ValueType rhs) noexcept -> EnumType& // + requires requires { lhs | rhs; } +{ + lhs = lhs | rhs; + return lhs; +} + +// value |= flag => value +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +constexpr auto operator|=(ValueType& lhs, const EnumType rhs) noexcept -> ValueType& // + requires requires { lhs | rhs; } +{ + lhs = lhs | rhs; + return lhs; +} + +// flag |= flag => flag +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +constexpr auto operator|=(EnumType& lhs, const EnumType rhs) noexcept -> EnumType& // + requires requires { lhs | rhs; } +{ + lhs = lhs | rhs; + return lhs; +} + +// ============================================================================= +// operator& + +// flag & value => flag +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +[[nodiscard]] constexpr auto operator&(const EnumType lhs, const ValueType rhs) noexcept -> EnumType // + requires requires { std::to_underlying(lhs) & rhs; } +{ + return static_cast(std::to_underlying(lhs) & rhs); +} + +// value & flag => value +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +[[nodiscard]] constexpr auto operator&(const ValueType lhs, const EnumType rhs) noexcept -> ValueType // + requires requires { lhs & std::to_underlying(rhs); } +{ + return static_cast(lhs & std::to_underlying(rhs)); +} + +// flag & flag => flag +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +[[nodiscard]] constexpr auto operator&(const EnumType lhs, const EnumType rhs) noexcept -> EnumType // + requires requires { std::to_underlying(lhs) & std::to_underlying(rhs); } +{ + return static_cast(std::to_underlying(lhs) & std::to_underlying(rhs)); +} + +// flag &= value => flag +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +constexpr auto operator&=(EnumType& lhs, const ValueType rhs) noexcept -> EnumType& // + requires requires { lhs & rhs; } +{ + lhs = lhs & rhs; + return lhs; +} + +// value &= flag => value +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +constexpr auto operator&=(ValueType& lhs, const EnumType rhs) noexcept -> ValueType& // + requires requires { lhs & rhs; } +{ + lhs = lhs & rhs; + return lhs; +} + +// flag &= flag => flag +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +constexpr auto operator&=(EnumType& lhs, const EnumType rhs) noexcept -> EnumType& // + requires requires { lhs & rhs; } +{ + lhs = lhs & rhs; + return lhs; +} + +// ============================================================================= +// operator^ + +// flag ^ value => flag +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +[[nodiscard]] constexpr auto operator^(const EnumType lhs, const ValueType rhs) noexcept -> EnumType // + requires requires { std::to_underlying(lhs) ^ rhs; } +{ + return static_cast(std::to_underlying(lhs) ^ rhs); +} + +// value ^ flag => value +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +[[nodiscard]] constexpr auto operator^(const ValueType lhs, const EnumType rhs) noexcept -> ValueType // + requires requires { lhs ^ std::to_underlying(rhs); } +{ + return static_cast(lhs ^ std::to_underlying(rhs)); +} + +// flag ^ flag => flag +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +[[nodiscard]] constexpr auto operator^(const EnumType lhs, const EnumType rhs) noexcept -> EnumType // + requires requires { std::to_underlying(lhs) ^ std::to_underlying(rhs); } +{ + return static_cast(std::to_underlying(lhs) ^ std::to_underlying(rhs)); +} + +// flag ^= value => flag +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +constexpr auto operator^=(EnumType& lhs, const ValueType rhs) noexcept -> EnumType& // + requires requires { lhs ^ rhs; } +{ + lhs = lhs ^ rhs; + return lhs; +} + +// value ^= flag => value +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +constexpr auto operator^=(ValueType& lhs, const EnumType rhs) noexcept -> ValueType& // + requires requires { lhs ^ rhs; } +{ + lhs = lhs ^ rhs; + return lhs; +} + +// flag ^= flag => flag +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +constexpr auto operator^=(EnumType& lhs, const EnumType rhs) noexcept -> EnumType& // + requires requires { lhs ^ rhs; } +{ + lhs = lhs ^ rhs; + return lhs; +} + +// ============================================================================= +// operator~ + +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +[[nodiscard]] constexpr auto operator~(const EnumType e) noexcept -> EnumType // + requires requires { ~std::to_underlying(e); } +{ + return static_cast(~std::to_underlying(e)); +} + +// ============================================================================= +// operator! + +template + requires std::is_scoped_enum_v and gal::prometheus::meta::user_defined::enum_is_flag::value +[[nodiscard]] constexpr auto operator!(const EnumType e) noexcept -> bool // + requires requires { !std::to_underlying(e); } +{ + return !std::to_underlying(e); +} diff --git a/src/functional/flag.ixx b/src/functional/flag.ixx deleted file mode 100644 index 14027f1e..00000000 --- a/src/functional/flag.ixx +++ /dev/null @@ -1,178 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.functional:flag; - -import std; - -#else -#pragma once - -#include - -#include - -#endif - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::functional) -{ - namespace operators - { - template - requires std::is_scoped_enum_v and std::is_convertible_v> - [[nodiscard]] constexpr auto operator|(const EnumType lhs, const Enum rhs) noexcept -> EnumType - { - return static_cast(lhs | std::to_underlying(rhs)); - } - - template - requires std::is_scoped_enum_v and std::is_convertible_v> - [[nodiscard]] constexpr auto operator|(const Enum lhs, const EnumType rhs) noexcept -> Enum - { - return static_cast(std::to_underlying(lhs) | rhs); - } - - template - requires std::is_scoped_enum_v - [[nodiscard]] constexpr auto operator|(const Enum lhs, const Enum rhs) noexcept -> Enum - { - return static_cast(std::to_underlying(lhs) | std::to_underlying(rhs)); - } - - template - requires std::is_scoped_enum_v and std::is_convertible_v> - [[nodiscard]] constexpr auto operator&(const EnumType lhs, const Enum rhs) noexcept -> EnumType - { - return static_cast(lhs & std::to_underlying(rhs)); - } - - template - requires std::is_scoped_enum_v and std::is_convertible_v> - [[nodiscard]] constexpr auto operator&(const Enum lhs, const EnumType rhs) noexcept -> Enum - { - return static_cast(std::to_underlying(lhs) & rhs); - } - - template - requires std::is_scoped_enum_v - [[nodiscard]] constexpr auto operator&(const Enum lhs, const Enum rhs) noexcept -> Enum - { - return static_cast(std::to_underlying(lhs) & std::to_underlying(rhs)); - } - } - - enum class EnumCheckPolicy - { - // contains: (flag & e) == e - // exclude: (flag & e) == 0 - ALL_BITS, - // contains: (flag & e) != 0 - // exclude: (flag & e) != e - ANY_BIT, - }; - - enum class EnumFoldPolicy - { - LOGICAL_OR, - LOGICAL_AND, - }; - - template... Enums> - // requires std::is_scoped_enum_v and std::is_convertible_v> - requires std::is_enum_v and std::is_convertible_v> - [[nodiscard]] constexpr auto contains(const EnumType flag, const Enum e, const Enums... es) noexcept -> bool - { - if constexpr (sizeof...(es) == 0) - { - using namespace operators; - if constexpr (CheckPolicy == EnumCheckPolicy::ALL_BITS) - { - return (e & flag) == e; - } - else if constexpr (CheckPolicy == EnumCheckPolicy::ANY_BIT) - { - return (flag & e) != 0; - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - } - else - { - if constexpr (FoldPolicy == EnumFoldPolicy::LOGICAL_AND) - { - return contains(flag, e) and contains(flag, es...); - } - else if constexpr (FoldPolicy == EnumFoldPolicy::LOGICAL_OR) - { - return contains(flag, e) or contains(flag, es...); - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - } - } - - template... Enums> - // requires std::is_scoped_enum_v - requires std::is_enum_v - [[nodiscard]] constexpr auto contains(const Enum flag, const Enum e, const Enums... es) noexcept -> bool - { - return contains(std::to_underlying(flag), e, es...); - } - - template... Enums> - // requires std::is_scoped_enum_v and std::is_convertible_v> - requires std::is_enum_v and std::is_convertible_v> - [[nodiscard]] constexpr auto exclude(const EnumType flag, const Enum e, const Enums... es) noexcept -> bool - { - if constexpr (sizeof...(es) == 0) - { - using namespace operators; - if constexpr (CheckPolicy == EnumCheckPolicy::ALL_BITS) - { - return (flag & e) == 0; - } - else if constexpr (CheckPolicy == EnumCheckPolicy::ANY_BIT) - { - return (flag & e) != e; - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - } - else - { - if constexpr (FoldPolicy == EnumFoldPolicy::LOGICAL_AND) - { - return exclude(flag, e) and exclude(flag, es...); - } - else if constexpr (FoldPolicy == EnumFoldPolicy::LOGICAL_OR) - { - return exclude(flag, e) or exclude(flag, es...); - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - } - } - - template... Enums> - // requires std::is_scoped_enum_v - requires std::is_enum_v - [[nodiscard]] constexpr auto exclude(const Enum flag, const Enum e, const Enums... es) noexcept -> bool - { - return exclude(std::to_underlying(flag), e, es...); - } -} diff --git a/src/functional/function_ref.hpp b/src/functional/function_ref.hpp new file mode 100644 index 00000000..b0c3deb1 --- /dev/null +++ b/src/functional/function_ref.hpp @@ -0,0 +1,245 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include + +#include + +#include + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace gal::prometheus::functional +{ + template + struct function_reference_wrapper; + + namespace function_ref_detail + { + template + struct compatible_function_pointer + { + using type = void; + + template + constexpr static auto value = false; + }; + + template + requires std::is_function_v>>> + struct compatible_function_pointer + { + using type = std::decay_t; + + template + constexpr static auto value = + std::is_invocable_v and + (std::is_void_v or std::is_convertible_v, Return>); + }; + + template + struct reference_wrapper; + + template + struct is_reference_wrapper : std::false_type {}; + + template + struct is_reference_wrapper> : std::true_type {}; + + template typename Wrapper> + requires std::is_base_of_v, Wrapper> + struct is_reference_wrapper> : std::true_type {}; + + template + constexpr auto is_reference_wrapper_v = is_reference_wrapper::value; + + template + concept reference_wrapper_t = is_reference_wrapper_v; + + template + struct reference_wrapper + { + using result_type = Return; + using signature = result_type(Args...); + + using arguments = std::tuple; + constexpr static auto argument_size = std::tuple_size_v; + template + requires (Index < argument_size) + using argument_type = std::tuple_element_t; + + private: + using function_pointer = void (*)(); + using functor_pointer = void*; + + using data_type = AlignedUnion; + using invoker = result_type (*)(data_type, Args...); + + struct constructor_tag {}; + + data_type data_; + invoker invoker_; + + template + constexpr static auto do_invoke_function( + data_type data, + Args... args + ) + noexcept(noexcept( + static_cast(std::invoke(reinterpret_cast(data.load()), static_cast(args)...)) + )) + -> result_type + { + auto pointer = data.load(); + auto function = reinterpret_cast(pointer); // NOLINT(clang-diagnostic-cast-function-type-strict) + + return static_cast(std::invoke(function, static_cast(args)...)); + } + + template + constexpr static auto do_invoke_functor( + data_type data, + Args... args + ) + noexcept(noexcept( + static_cast(std::invoke(*static_cast(data.load()), static_cast(args)...) + )) + ) + -> result_type + { + auto pointer = data.load(); + auto& functor = *static_cast(pointer); + + return static_cast(std::invoke(functor, static_cast(args)...)); + } + + template + constexpr reference_wrapper(constructor_tag, FunctionPointer function) noexcept + : data_{data_type::constructor_tag{}, reinterpret_cast(function)}, + // NOLINT(clang-diagnostic-cast-function-type-strict) + invoker_{&do_invoke_function::type>} + { + // throw exception? + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(function != nullptr, "function pointer must not be null"); + } + + public: + template + requires compatible_function_pointer::template value // + constexpr explicit(false) reference_wrapper(FunctionPointer function) + noexcept(std::is_nothrow_constructible_v) + : reference_wrapper{constructor_tag{}, function} {} + + /** + * @brief Creates a reference to the function created by the stateless lambda. + */ + template + requires + (not compatible_function_pointer::template value) and + std::is_invocable_v and + (std::is_void_v or std::is_convertible_v, Return>) and + requires(StatelessLambda& functor) + { + (+functor)(std::declval()...); + } + // note: const reference to avoid ambiguous + constexpr explicit(false) reference_wrapper(const StatelessLambda& functor) + : reference_wrapper{constructor_tag{}, +functor} {} + + /** + * @brief Creates a reference to the specified functor. + * It will store a pointer to the function object, so it must live as long as the reference. + */ + template + requires + (not compatible_function_pointer::template value) and + (not is_reference_wrapper_v) and + std::is_invocable_v and + (std::is_void_v or std::is_convertible_v, Return>) + constexpr explicit(false) reference_wrapper(Functor& functor) noexcept + : data_{data_type::constructor_tag{}, const_cast(static_cast(&functor))}, + invoker_{&do_invoke_functor} {} + + constexpr auto operator()(Args... args) const noexcept(noexcept(std::invoke(invoker_, data_, static_cast(args)...))) + { + return std::invoke(invoker_, data_, static_cast(args)...); + } + }; + } + + /** + * @brief A reference to a function. + * This is a lightweight reference to a function. + * It can refer to any function that is compatible with given signature. + * + * A function is compatible if it is callable with regular function call syntax from the given argument types, + * and its return type is either implicitly convertible to the specified return type or the specified return type is `void`. + * + * In general, it will store a pointer to the functor, requiring a lvalue. + * But if it is created with a function pointer or something convertible to a function pointer, + * it will store the function pointer itself. + */ + template + struct function_reference_wrapper : function_ref_detail::reference_wrapper + { + using function_ref_detail::reference_wrapper::reference_wrapper; + }; + + template + [[nodiscard]] constexpr auto ref(T&& object) + noexcept(std::is_nothrow_constructible_v, T>) + -> function_reference_wrapper + { + return function_reference_wrapper{std::forward(object)}; + } + + namespace function_ref_detail + { + template + struct signature + { + template + [[nodiscard]] constexpr auto operator+(Object&& object) const + noexcept(std::is_nothrow_constructible_v, Object>) + -> function_reference_wrapper + { + return function_reference_wrapper{std::forward(object)}; + } + }; + + template + struct to; + + template + struct take + { + template + [[nodiscard]] constexpr auto operator+(const to) const noexcept -> signature + { + return {}; + } + }; + + template + struct to + { + template + [[nodiscard]] constexpr auto operator+(const take) const noexcept -> signature + { + return {}; + } + }; + } + + template + constexpr auto to = function_ref_detail::to{}; + + template + constexpr auto take = function_ref_detail::take{}; +} diff --git a/src/functional/function_ref.ixx b/src/functional/function_ref.ixx deleted file mode 100644 index f117f757..00000000 --- a/src/functional/function_ref.ixx +++ /dev/null @@ -1,188 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.functional:function_ref; - -import std; -import gal.prometheus.error; - -import :aligned_union; - -#else -#pragma once - -#include -#include - -#include -#include -#include - -#endif - -namespace gal::prometheus::functional -{ - namespace function_ref_detail - { - template - struct compatible_function_pointer - { - using type = void; - - template - constexpr static auto value = false; - }; - - template - requires std::is_function_v>>> - struct compatible_function_pointer - { - using type = std::decay_t; - - template - constexpr static auto value = - std::is_invocable_v and - (std::is_void_v or std::is_convertible_v, Return>); - }; - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - template - class FunctionRef; - - template - constexpr auto is_function_ref_v = false; - - template - constexpr auto is_function_ref_v> = true; - - template - concept function_ref = is_function_ref_v; - - /** - * @brief A reference to a function. - * This is a lightweight reference to a function. - * It can refer to any function that is compatible with given signature. - * - * A function is compatible if it is callable with regular function call syntax from the given argument types, - * and its return type is either implicitly convertible to the specified return type or the specified return type is `void`. - * - * In general, it will store a pointer to the functor, requiring a lvalue. - * But if it is created with a function pointer or something convertible to a function pointer, - * it will store the function pointer itself. - */ - template - class FunctionRef - { - public: - using result_type = Return; - using signature = Return(Args...); - - using arguments = std::tuple; - constexpr static auto argument_size = std::tuple_size_v; - template - using argument_type = std::tuple_element_t; - - private: - using function_pointer = void (*)(); - using functor_pointer = void*; - - using data_type = AlignedUnion; - - using invoker = result_type (*)(data_type, Args...); - - struct constructor_tag {}; - - data_type data_; - invoker invoker_; - - template - constexpr static auto do_invoke_function( - data_type data, - Args... args) // - noexcept(noexcept( - static_cast(std::invoke(reinterpret_cast(data.load()), static_cast(args)...)) - )) - -> result_type - { - auto pointer = data.load(); - auto function = reinterpret_cast(pointer); // NOLINT(clang-diagnostic-cast-function-type-strict) - - return static_cast(std::invoke(function, static_cast(args)...)); - } - - template - constexpr static auto do_invoke_functor( - data_type data, - Args... args) // - noexcept(noexcept( - static_cast(std::invoke(*static_cast(data.load()), static_cast(args)...) - )) - ) - -> result_type - { - auto pointer = data.load(); - auto& functor = *static_cast(pointer); - - return static_cast(std::invoke(functor, static_cast(args)...)); - } - - template - constexpr FunctionRef(constructor_tag, FunctionPointer function) noexcept - : data_{data_type::constructor_tag{}, reinterpret_cast(function)}, - // NOLINT(clang-diagnostic-cast-function-type-strict) - invoker_{&do_invoke_function::type>} - { - // throw exception? - GAL_PROMETHEUS_DEBUG_NOT_NULL(function, "function pointer must not be null"); - } - - public: - template - requires function_ref_detail::compatible_function_pointer::template value // - constexpr explicit(false) FunctionRef(FunctionPointer function) - noexcept(std::is_nothrow_constructible_v) - : FunctionRef{constructor_tag{}, function} {} - - /** - * @brief Creates a reference to the function created by the stateless lambda. - */ - template - requires(not function_ref_detail::compatible_function_pointer::template value) and - std::is_invocable_v and - (std::is_void_v or std::is_convertible_v, Return>) and - requires(StatelessLambda& functor) - { - (+functor)(std::declval()...); - } - // note: const reference to avoid ambiguous - constexpr explicit(false) FunctionRef(const StatelessLambda& functor) - : FunctionRef{constructor_tag{}, +functor} {} - - /** - * @brief Creates a reference to the specified functor. - * It will store a pointer to the function object, so it must live as long as the reference. - */ - template - requires(not function_ref_detail::compatible_function_pointer::template value) and - (not is_function_ref_v) and - std::is_invocable_v and - (std::is_void_v or std::is_convertible_v, Return>) - constexpr explicit(false) FunctionRef(Functor& functor) noexcept - : data_{data_type::constructor_tag{}, const_cast(static_cast(&functor))}, - invoker_{&do_invoke_functor} {} - - constexpr auto operator()(Args... args) const noexcept(noexcept(std::invoke(invoker_, data_, static_cast(args)...))) - { - return std::invoke(invoker_, data_, static_cast(args)...); - } - }; - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -} diff --git a/src/functional/function_signature.ixx b/src/functional/function_signature.ixx index cfbb2647..735575bf 100644 --- a/src/functional/function_signature.ixx +++ b/src/functional/function_signature.ixx @@ -3,16 +3,18 @@ // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#if GAL_PROMETHEUS_USE_MODULE -module; +#if not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED #include -export module gal.prometheus.functional:function_signature; +export module gal.prometheus:functional.function_signature; import std; -#else +#endif not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED + +#if not GAL_PROMETHEUS_USE_MODULE + #pragma once #include @@ -23,7 +25,11 @@ import std; #endif -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::functional) +#if GAL_PROMETHEUS_INTELLISENSE_WORKING +namespace GAL_PROMETHEUS_COMPILER_MODULE_NAMESPACE_PREFIX :: functional +#else +GAL_PROMETHEUS_COMPILER_MODULE_NAMESPACE_EXPORT(functional) +#endif { template< typename ReturnType, diff --git a/src/functional/functional.hpp b/src/functional/functional.hpp new file mode 100644 index 00000000..c41c33fc --- /dev/null +++ b/src/functional/functional.hpp @@ -0,0 +1,14 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include diff --git a/src/functional/functional.ixx b/src/functional/functional.ixx deleted file mode 100644 index 18df63bf..00000000 --- a/src/functional/functional.ixx +++ /dev/null @@ -1,30 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -export module gal.prometheus.functional; - -export import :type_list; -export import :value_list; -export import :functor; -export import :aligned_union; -export import :function_ref; -export import :math; -export import :flag; -export import :function_signature; - -#else -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#endif diff --git a/src/functional/functor.ixx b/src/functional/functor.hpp similarity index 52% rename from src/functional/functor.ixx rename to src/functional/functor.hpp index a1259ba9..7b12a564 100644 --- a/src/functional/functor.ixx +++ b/src/functional/functor.hpp @@ -1,28 +1,17 @@ // This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal +// Copyright (C) 2022-2025 Life4gal // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.functional:functor; - -import std; - -#else #pragma once #include #include +#include #include -#endif - -#if __cpp_static_call_operator >= 202207L +#if defined(__cpp_static_call_operator) and __cpp_static_call_operator >= 202207L #define FUNCTOR_WORKAROUND_OPERATOR_STATIC static #define FUNCTOR_WORKAROUND_OPERATOR_CONST #define FUNCTOR_WORKAROUND_OPERATOR_THIS(type) type:: @@ -34,61 +23,28 @@ import std; namespace gal::prometheus::functional { - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - - template - struct y_combinator - { - using function_type = FunctionType; - - function_type function; - - template - constexpr auto operator()(Args&&... args) const - noexcept(std::is_nothrow_invocable_v) -> - // https://github.com/llvm/llvm-project/issues/97680 - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - decltype(std::invoke(function, *this, std::forward(args)...)) - #else - decltype(auto) // - #endif - { - // we pass ourselves to function, the lambda should take the first argument as `auto&& self` or similar. - return std::invoke(function, *this, std::forward(args)...); - } - }; - - template - struct overloaded : Ts... - { - constexpr explicit overloaded(Ts&&... ts) noexcept((std::is_nothrow_constructible_v and ...)) - : Ts{std::forward(ts)}... {} - }; - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END - namespace functor_detail { - enum class InvokeFoldType + enum class UnaryFoldCategory : std::uint8_t { ALL, ANY, NONE, }; - template - struct unary_invoker + template + struct unary { template [[nodiscard]] constexpr FUNCTOR_WORKAROUND_OPERATOR_STATIC auto operator()(const Args&... args) FUNCTOR_WORKAROUND_OPERATOR_CONST - noexcept((std::is_nothrow_invocable_r_v and ...)) -> bool // - requires(std::is_invocable_r_v and ...) + noexcept((std::is_nothrow_invocable_r_v and ...)) -> bool // + requires(std::is_invocable_r_v and ...) { if constexpr (sizeof...(Args) == 0) { return true; } - if constexpr (FoldType == InvokeFoldType::ALL) { return (DefaultFunctor(args) and ...); } - else if constexpr (FoldType == InvokeFoldType::ANY) { return (DefaultFunctor(args) or ...); } - else if constexpr (FoldType == InvokeFoldType::NONE) { return not(DefaultFunctor(args) or ...); } + if constexpr (Category == UnaryFoldCategory::ALL) { return (Default(args) and ...); } + else if constexpr (Category == UnaryFoldCategory::ANY) { return (Default(args) or ...); } + else if constexpr (Category == UnaryFoldCategory::NONE) { return not(Default(args) or ...); } else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } } @@ -102,15 +58,15 @@ namespace gal::prometheus::functional { if constexpr (sizeof...(Args) == 0) { return true; } - if constexpr (FoldType == InvokeFoldType::ALL) { return (function(args) and ...); } - else if constexpr (FoldType == InvokeFoldType::ANY) { return (function(args) or ...); } - else if constexpr (FoldType == InvokeFoldType::NONE) { return not(function(args) or ...); } + if constexpr (Category == UnaryFoldCategory::ALL) { return (function(args) and ...); } + else if constexpr (Category == UnaryFoldCategory::ANY) { return (function(args) or ...); } + else if constexpr (Category == UnaryFoldCategory::NONE) { return not(function(args) or ...); } else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } } }; - template - struct binary_invoker + template + struct binary { template [[nodiscard]] constexpr FUNCTOR_WORKAROUND_OPERATOR_STATIC auto @@ -120,17 +76,17 @@ namespace gal::prometheus::functional const Reset&... reset // ) FUNCTOR_WORKAROUND_OPERATOR_CONST noexcept( - noexcept(DefaultFunctor(lhs, rhs)) and // - noexcept((DefaultFunctor(lhs, reset) and ...)) and // - noexcept((DefaultFunctor(rhs, reset) and ...)) // + noexcept(Default(lhs, rhs)) and // + noexcept((Default(lhs, reset) and ...)) and // + noexcept((Default(rhs, reset) and ...)) // ) -> const auto& { - if constexpr (sizeof...(reset) == 0) { return DefaultFunctor(lhs, rhs) ? lhs : rhs; } + if constexpr (sizeof...(reset) == 0) { return Default(lhs, rhs) ? lhs : rhs; } else { return - FUNCTOR_WORKAROUND_OPERATOR_THIS(binary_invoker)operator()( - FUNCTOR_WORKAROUND_OPERATOR_THIS(binary_invoker)operator()(lhs, rhs), + FUNCTOR_WORKAROUND_OPERATOR_THIS(binary)operator()( + FUNCTOR_WORKAROUND_OPERATOR_THIS(binary)operator()(lhs, rhs), reset... ); } @@ -154,49 +110,61 @@ namespace gal::prometheus::functional else { return - FUNCTOR_WORKAROUND_OPERATOR_THIS(binary_invoker)operator()( - FUNCTOR_WORKAROUND_OPERATOR_THIS(binary_invoker)operator()(lhs, rhs), + FUNCTOR_WORKAROUND_OPERATOR_THIS(binary)operator()( + FUNCTOR_WORKAROUND_OPERATOR_THIS(binary)operator()(lhs, rhs), reset... ); } } }; - [[maybe_unused]] constexpr auto as_boolean = [](const auto& i) noexcept((static_cast(i))) -> bool { return static_cast(i); }; - [[maybe_unused]] constexpr auto compare_greater_than = [](const auto& lhs, const auto& rhs) noexcept(noexcept(lhs > rhs)) -> bool + [[maybe_unused]] constexpr auto to_boolean = [](const auto& object) noexcept(noexcept(static_cast(object))) -> bool { - return lhs > rhs; - }; - [[maybe_unused]] constexpr auto compare_greater_equal = [](const auto& lhs, const auto& rhs) noexcept(noexcept(lhs >= rhs)) -> bool - { - return lhs >= rhs; - }; - [[maybe_unused]] constexpr auto compare_less_than = [](const auto& lhs, const auto& rhs) noexcept(noexcept(lhs < rhs)) -> bool - { - return lhs < rhs; - }; - [[maybe_unused]] constexpr auto compare_less_equal = [](const auto& lhs, const auto& rhs) noexcept(noexcept(lhs <= rhs)) -> bool - { - return lhs <= rhs; - }; - [[maybe_unused]] constexpr auto compare_equal = [](const auto& lhs, const auto& rhs) noexcept(noexcept(lhs == rhs)) -> bool - { - return lhs == rhs; - }; - [[maybe_unused]] constexpr auto compare_not_equal = [](const auto& lhs, const auto& rhs) noexcept(noexcept(lhs != rhs)) -> bool - { - return lhs != rhs; + return static_cast(object); }; } - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(functor) + template + // [[deprecated("use `deducing this` instead")]] + struct y_combinator + { + using function_type = FunctionType; + + function_type function; + + template + constexpr auto operator()(Args&&... args) const + noexcept(std::is_nothrow_invocable_v) -> + // https://github.com/llvm/llvm-project/issues/97680 + #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) + decltype(std::invoke(function, *this, std::forward(args)...)) + #else + decltype(auto) // + #endif + { + // we pass ourselves to function, the lambda should take the first argument as `auto&& self` or similar. + return std::invoke(function, *this, std::forward(args)...); + } + }; + + template + struct overloaded : Ts... + { + constexpr explicit overloaded(Ts&&... ts) noexcept((std::is_nothrow_constructible_v and ...)) + : Ts{std::forward(ts)}... {} + + // This makes all overloads viable to participate in the resolution + using Ts::operator()...; + }; + + namespace functor { - constexpr functor_detail::unary_invoker all; - constexpr functor_detail::unary_invoker any; - constexpr functor_detail::unary_invoker none; + constexpr functor_detail::unary all{}; + constexpr functor_detail::unary any{}; + constexpr functor_detail::unary none{}; - constexpr functor_detail::binary_invoker max; - constexpr functor_detail::binary_invoker min; + constexpr functor_detail::binary max{}; + constexpr functor_detail::binary min{}; } } diff --git a/src/functional/hash.hpp b/src/functional/hash.hpp new file mode 100644 index 00000000..acc2e228 --- /dev/null +++ b/src/functional/hash.hpp @@ -0,0 +1,119 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include + +#include + +namespace gal::prometheus::functional +{ + using hash_result_type = std::uint64_t; + + namespace hash_detail + { + template + struct hash + { + using value_type = T; + using hash_type = Hash; + + [[nodiscard]] constexpr auto operator()(const value_type& value) const noexcept -> hash_result_type + { + return hash_type{}(value); + } + }; + + template + requires std::is_same_v + struct hash + { + using is_transparent = int; + using value_type = String; + using hash_type = Hash; + + [[nodiscard]] constexpr auto operator()(const std::ranges::viewable_range auto string) const noexcept -> hash_result_type + { + // FNV-1a hash. See: http://www.isthe.com/chongo/tech/comp/fnv/ + static_assert(sizeof(hash_result_type) == 8); + constexpr hash_result_type hash_init{14695981039346656037ull}; + constexpr hash_result_type hash_prime{1099511628211ull}; + + auto result = hash_init; + std::ranges::for_each( + string, + [&result](const auto v) noexcept -> void + { + result ^= v; + result *= hash_prime; + } + ); + return result; + } + + template + [[nodiscard]] constexpr auto operator()(S&& string) const noexcept -> hash_result_type + { + return this->operator()(std::forward(string) | std::views::all); + } + }; + + template + requires std::is_pointer_v + struct hash + { + using is_transparent = int; + using value_type = Pointer; + using hash_type = Hash; + + [[nodiscard]] constexpr auto operator()(const value_type pointer) const noexcept -> hash_result_type + { + return hash_type{}(pointer); + } + + template + requires std::is_base_of_v::element_type, typename std::pointer_traits::element_type> + [[nodiscard]] constexpr auto operator()(const U pointer) const noexcept -> hash_result_type + { + return hash_type{}(pointer); + } + }; + } + + template> + constexpr auto hash = hash_detail::hash{}; + + [[nodiscard]] constexpr auto hash_combine_2(const hash_result_type hash1, const hash_result_type hash2) noexcept -> hash_result_type + { + static_assert(sizeof(hash_result_type) == 8); + return hash1 + 0x9e3779b97f681800ull + (hash2 << 6) + (hash2 >> 2); + } + + template + [[nodiscard]] constexpr auto hash_combine(First&& first, Second&& second, Reset&&... reset) noexcept -> hash_result_type requires requires + { + hash>; + hash>; + (hash>, ...); + } + { + if constexpr (sizeof...(Reset) == 0) + { + const auto hash1 = hash>(std::forward(first)); + const auto hash2 = hash>(std::forward(second)); + return functional::hash_combine_2(hash1, hash2); + } + else + { + const auto hash1 = hash>(std::forward(first)); + const auto hash2 = hash_combine(std::forward(second), std::forward(reset)...); + return functional::hash_combine_2(hash1, hash2); + } + } +} diff --git a/src/functional/math.ixx b/src/functional/math.ixx deleted file mode 100644 index 34eab927..00000000 --- a/src/functional/math.ixx +++ /dev/null @@ -1,423 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include -#if __has_include() -#include -#endif -#if __has_include() -#include -#endif - -export module gal.prometheus.functional:math; - -import std; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -#else -#pragma once - -#include -#include -#if __has_include() -#include -#endif -#if __has_include() -#include -#endif - -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -#endif - -namespace gal::prometheus::functional -{ - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - - template - requires std::is_arithmetic_v - [[nodiscard]] constexpr auto is_nan(const T value) noexcept -> bool - { - if constexpr (not std::is_floating_point_v) - { - return false; - } - else - { - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED - { - // - return value != value; - } - - return std::isnan(value); - } - } - - template - requires std::is_arithmetic_v - [[nodiscard]] constexpr auto abs(const T value) noexcept -> T - { - if constexpr (std::is_unsigned_v) - { - return value; - } - else - { - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED - { - // - return value > 0 ? value : -value; - } - - return std::abs(value); - } - } - - template - requires std::is_arithmetic_v - [[nodiscard]] constexpr auto floor(const T value) noexcept -> T - { - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED - { - if constexpr (std::is_integral_v) { return value; } - else - { - if (value >= 0 or static_cast(static_cast(value)) == value) - { - return static_cast(static_cast(value)); - } - - return static_cast(static_cast(value) - 1); - } - } - - return std::floor(value); - } - - template - requires std::is_arithmetic_v - [[nodiscard]] constexpr auto ceil(const T value) noexcept -> T - { - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED - { - if constexpr (std::is_integral_v) { return value; } - else - { - if (value >= 0 or static_cast(static_cast(value)) == value) - { - return static_cast(static_cast(value)); - } - - return static_cast(static_cast(value) + 1); - } - } - - return std::ceil(value); - } - - template - requires std::is_arithmetic_v - [[nodiscard]] constexpr auto tgamma(const T value) noexcept -> T - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(value >= 0); - - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED - { - // - return (value <= 1) ? 1 : (value * functional::tgamma(value - 1)); - } - - return static_cast(std::tgamma(value)); - } - - template - [[nodiscard]] constexpr auto factorial(const T value) noexcept -> T // - { - return functional::tgamma(value); - } - - template - requires std::is_arithmetic_v - [[nodiscard]] constexpr auto pow(const T base, const int exp) noexcept -> T - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(exp >= 0); - - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED - { - if (exp == 0) { return static_cast(1); } - - return static_cast(base * functional::pow(base, exp - 1)); - } - - return static_cast(std::pow(base, exp)); - } - - template - requires std::is_arithmetic_v - [[nodiscard]] constexpr auto sqrt(const T value) noexcept -> T - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(value >= 0); - - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED - { - if (value == 0) // NOLINT(clang-diagnostic-float-equal) - { - return value; - } - - T prev = 0; - T current = value / 2; - - while (current != prev) // NOLINT(clang-diagnostic-float-equal) - { - prev = current; - current = (current + value / current) / 2; - } - - return current; - } - - return static_cast(std::sqrt(value)); - } - - template - requires std::is_arithmetic_v - [[nodiscard]] constexpr auto hypot(const T x, const std::type_identity_t y) noexcept -> T - { - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED - { - // - return functional::sqrt(functional::pow(x, 2) + functional::pow(y, 2)); - } - - return static_cast(std::hypot(x, y)); - } - - template - requires std::is_arithmetic_v - [[nodiscard]] constexpr auto hypot(const T x, const std::type_identity_t y, const std::type_identity_t z) noexcept -> T - { - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED - { - // - return functional::sqrt(functional::pow(x, 2) + functional::pow(y, 2) + functional::pow(z, 2)); - } - - return static_cast(std::hypot(x, y, z)); - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END - - namespace math_detail - { - template - [[nodiscard]] constexpr auto tan_series_exp(const T value) noexcept -> T - { - const auto z = value - std::numbers::pi_v / 2; - - if (std::numeric_limits::min() > functional::abs(z)) { return std::numbers::pi_v / 2; } - - // this is based on a fourth-order expansion of tan(z) using Bernoulli numbers - return -1 / z + (z / 3 + (functional::pow(z, 3) / 45 + (2 * functional::pow(z, 5) / 945 + functional::pow(z, 7) / 4725))); - } - - template - [[nodiscard]] constexpr auto tan_cf_recurse(const T value, const int current, const int max) noexcept -> T - { - const auto z = static_cast(2 * current - 1); - - if (current < max) { return z - value / tan_cf_recurse(value, current + 1, max); } - - return z; - } - - template - [[nodiscard]] constexpr auto tan_cf_main(const T value) noexcept -> T - { - if (value > static_cast(1.55) and value < static_cast(1.6)) - { - // deals with a singularity at tan(pi/2) - return tan_series_exp(value); - } - - if (value > static_cast(1.4)) { return value / tan_cf_recurse(value * value, 1, 45); } - - if (value > static_cast(1)) { return value / tan_cf_recurse(value * value, 1, 35); } - - return value / tan_cf_recurse(value * value, 1, 25); - } - - template - [[nodiscard]] constexpr auto tan_begin(const T value, const int count = 0) noexcept -> T - { - if (value > std::numbers::pi_v) - { - if (count > 1) { return std::numeric_limits::quiet_NaN(); } - - return tan_begin(value - std::numbers::pi_v * functional::floor(value - std::numbers::pi_v), count + 1); - } - - return tan_cf_main(value); - } - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - - template - requires std::is_arithmetic_v - [[nodiscard]] constexpr auto tan(const T value) noexcept -> T - { - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED - { - if (functional::is_nan(value)) { return std::numeric_limits::quiet_NaN(); } - - if (value < static_cast(0)) { return -math_detail::tan_begin(-value); } - - return math_detail::tan_begin(value); - } - - return std::tan(value); - } - - template - requires std::is_arithmetic_v - [[nodiscard]] constexpr auto sin(const T value) noexcept -> T - { - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED - { - if (functional::is_nan(value)) { return std::numeric_limits::quiet_NaN(); } - - if (std::numeric_limits::min() > functional::abs(value - std::numbers::pi_v / 2)) { return 1; } - - if (std::numeric_limits::min() > functional::abs(value + std::numbers::pi_v / 2)) { return -1; } - - if (std::numeric_limits::min() > functional::abs(value - std::numbers::pi_v)) { return 0; } - - if (std::numeric_limits::min() > functional::abs(value + std::numbers::pi_v)) { return -0; } - - // sin(x) = 2tan(x/2) / (1 + tan²(x/2)) - const auto z = functional::tan(value / static_cast(2)); - return (static_cast(2) * z) / (static_cast(1) + z * z); - } - - return static_cast(std::sin(value)); - } - - template - requires std::is_arithmetic_v - [[nodiscard]] constexpr auto cos(const T value) noexcept -> T - { - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED - { - if (functional::is_nan(value)) { return std::numeric_limits::quiet_NaN(); } - - if (std::numeric_limits::min() > functional::abs(value - std::numbers::pi_v / 2)) { return 0; } - - if (std::numeric_limits::min() > functional::abs(value + std::numbers::pi_v / 2)) { return -0; } - - if (std::numeric_limits::min() > functional::abs(value - std::numbers::pi_v)) { return -1; } - - if (std::numeric_limits::min() > functional::abs(value + std::numbers::pi_v)) { return -1; } - - // cos(x) = (1 - tan²(x/2)) / (1 + tan²(x/2)) - const auto z = functional::tan(value / static_cast(2)); - return (static_cast(1) - z * z) / (static_cast(1) + z * z); - } - - return static_cast(std::cos(value)); - } - - template - requires std::is_arithmetic_v - [[nodiscard]] constexpr auto normalize(const T x, const T y) noexcept -> std::pair - { - const auto d = functional::pow(x, 2) + functional::pow(y, 2); - const auto d2 = [d] - { - if constexpr (std::is_floating_point_v) - { - return d; - } - else - { - if constexpr (sizeof(T) == sizeof(float)) - { - return static_cast(d); - } - else if constexpr (sizeof(T) == sizeof(double)) - { - return static_cast(d); - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - } - }(); - - if (d2 > static_cast(0)) - { - // todo - - const auto inv_len = [d2] - { - if constexpr (sizeof(d2) == sizeof(float)) - { - #if defined(__AVX512F__) - __m512 d2_v = _mm512_set1_ps(d2); - __m512 inv_len_v = _mm512_rsqrt14_ps(d2_v); - return _mm512_cvtss_f32(inv_len_v); - #elif defined(__AVX__) - __m256 d2_v = _mm256_set1_ps(d2); - __m256 inv_len_v = _mm256_rsqrt_ps(d2_v); - return _mm256_cvtss_f32(inv_len_v); - #elif defined(__SSE4_1__) or defined(__SSE3__) or defined(__SSE__) - __m128 d2_v = _mm_set_ss(d2); - __m128 inv_len_v = _mm_rsqrt_ss(d2_v); - return _mm_cvtss_f32(inv_len_v); - #else - return 1.0f / functional::sqrt(d2); - #endif - } - else if constexpr (sizeof(d2) == sizeof(double)) - { - #if defined(__AVX512F__) - __m512d d2_v = _mm512_set1_pd(d2); - __m512d sqrt_d2_v = _mm512_sqrt_pd(d2_v); - __m512d inv_sqrt_d2_v = _mm512_div_pd(_mm512_set1_pd(1.0), sqrt_d2_v); - return _mm512_cvtsd_f64(inv_sqrt_d2_v); - #elif defined(__AVX__) - __m256d d2_v = _mm256_set1_pd(d2); - __m256d sqrt_d2_v = _mm256_sqrt_pd(d2_v); - __m256d inv_sqrt_d2_v = _mm256_div_pd(_mm256_set1_pd(1.0), sqrt_d2_v); - return _mm256_cvtsd_f64(inv_sqrt_d2_v); - #elif defined(__SSE4_1__) or defined(__SSE3__) or defined(__SSE__) - __m128d d2_v = _mm_set1_pd(d2); - __m128d sqrt_d2_v = _mm_sqrt_pd(d2_v); - __m128d inv_sqrt_d2_v = _mm_div_pd(_mm_set1_pd(1.0), sqrt_d2_v); - return _mm_cvtsd_f64(inv_sqrt_d2_v); - #else - return 1.0 / functional::sqrt(d2); - #endif - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - }(); - - return {static_cast(x * inv_len), static_cast(y * inv_len)}; - } - - return {x, y}; - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -} diff --git a/src/functional/type_list.ixx b/src/functional/type_list.hpp similarity index 64% rename from src/functional/type_list.ixx rename to src/functional/type_list.hpp index 813fcaf6..3e63273c 100644 --- a/src/functional/type_list.ixx +++ b/src/functional/type_list.hpp @@ -1,18 +1,8 @@ // This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal +// Copyright (C) 2022-2025 Life4gal // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.functional:type_list; - -import std; - -#else #pragma once #include @@ -20,8 +10,6 @@ import std; #include -#endif - #if defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) or defined(GAL_PROMETHEUS_COMPILER_GNU) #define TYPE_LIST_WORKAROUND_BINDER(T, Prediction) binder::template rebind #else @@ -155,38 +143,33 @@ namespace gal::prometheus::functional }; public: - [[nodiscard]] consteval auto size() const noexcept -> std::size_t + [[nodiscard]] constexpr static auto size() noexcept -> std::size_t { - (void)this; return types_size; } template typename Projection> - [[nodiscard]] consteval auto projection() const noexcept -> list::type...> // NOLINT(modernize-type-traits) + [[nodiscard]] constexpr static auto projection() noexcept -> list::type...> // NOLINT(modernize-type-traits) { - (void)this; return {}; } template typename Prediction> - [[nodiscard]] consteval auto all() const noexcept -> bool + [[nodiscard]] constexpr static auto all() noexcept -> bool { - (void)this; return ((Prediction::value) and ...); } template typename Prediction, typename T> - [[nodiscard]] consteval auto all() const noexcept -> bool + [[nodiscard]] constexpr static auto all() noexcept -> bool { - (void)this; - return this->template all(); + return list::all(); } template typename Prediction, typename... Us> requires(sizeof...(Us) > 1) - [[nodiscard]] consteval auto all(const list) const noexcept -> bool + [[nodiscard]] constexpr static auto all(const list) noexcept -> bool { - (void)this; return [](std::index_sequence) noexcept -> bool { return ((Prediction, typename list::template nth_type>::value) and ...); @@ -195,29 +178,33 @@ namespace gal::prometheus::functional template typename Prediction, typename... Us> requires(sizeof...(Us) > 1) - [[nodiscard]] consteval auto all() const noexcept -> bool + [[nodiscard]] constexpr static auto all() noexcept -> bool { - return this->template all(list{}); + return list::all(list{}); } template - [[nodiscard]] consteval auto all() const noexcept -> bool { return this->all(); } + [[nodiscard]] constexpr static auto all() noexcept -> bool + { + return list::all(); + } template typename Prediction> - [[nodiscard]] consteval auto any() const noexcept -> bool + [[nodiscard]] constexpr static auto any() noexcept -> bool { - (void)this; return ((Prediction::value) or ...); } template typename Prediction, typename T> - [[nodiscard]] consteval auto any() const noexcept -> bool { return this->template any(); } + [[nodiscard]] constexpr static auto any() noexcept -> bool + { + return list::any(); + } template typename Prediction, typename... Us> requires(sizeof...(Us) > 1) - [[nodiscard]] consteval auto any(const list) const noexcept -> bool + [[nodiscard]] constexpr static auto any(const list) noexcept -> bool { - (void)this; return [](std::index_sequence) noexcept -> bool { return ((Prediction, typename list::template nth_type>::value) or ...); @@ -226,37 +213,39 @@ namespace gal::prometheus::functional template typename Prediction, typename... Us> requires(sizeof...(Us) > 1) - [[nodiscard]] consteval auto any() const noexcept -> bool + [[nodiscard]] constexpr static auto any() noexcept -> bool { - return this->template any(list{}); + return list::any(list{}); } template - [[nodiscard]] consteval auto any() const noexcept -> bool + [[nodiscard]] constexpr static auto any() noexcept -> bool { - return this->any(); + return list::any(); } template typename Prediction> - requires(list{}.any()) - [[nodiscard]] consteval auto index_of() const noexcept -> std::size_t + requires(list::any()) + [[nodiscard]] constexpr static auto index_of() noexcept -> std::size_t { - (void)this; return index_of_impl<0, Prediction, Ts...>::value; } template typename Prediction> - requires requires(list l) { l.template index_of(); } - [[nodiscard]] consteval auto index_of() const noexcept -> std::size_t + requires requires { list::index_of(); } + [[nodiscard]] constexpr static auto index_of() noexcept -> std::size_t { - return this->template index_of(); + return list::index_of(); } template - requires requires(list l) { l.template index_of(); } - [[nodiscard]] consteval auto index_of() const noexcept -> std::size_t { return this->template index_of(); } + requires requires { list::index_of(); } + [[nodiscard]] constexpr static auto index_of() noexcept -> std::size_t + { + return list::index_of(); + } - [[nodiscard]] consteval auto reverse() const noexcept -> auto // + [[nodiscard]] constexpr static auto reverse() noexcept -> auto // { return [](std::index_sequence) noexcept -> auto // { @@ -265,37 +254,45 @@ namespace gal::prometheus::functional } template - [[nodiscard]] consteval auto push_back() const noexcept -> list + [[nodiscard]] constexpr static auto push_back() noexcept -> list { - (void)this; return {}; } template - [[nodiscard]] consteval auto push_back(const list) const noexcept -> list { return this->push_back(); } + [[nodiscard]] constexpr static auto push_back(const list) noexcept -> list + { + return list::push_back(); + } template - [[nodiscard]] consteval auto push_back() const noexcept -> auto { return this->push_back(List); } + [[nodiscard]] constexpr static auto push_back() noexcept -> auto + { + return list::push_back(List); + } template - [[nodiscard]] consteval auto push_front() const noexcept -> list + [[nodiscard]] constexpr static auto push_front() noexcept -> list { - (void)this; return {}; } template - [[nodiscard]] consteval auto push_front(const list) const noexcept -> list { return this->push_front(); } + [[nodiscard]] constexpr static auto push_front(const list) noexcept -> list + { + return list::push_front(); + } template - [[nodiscard]] consteval auto push_front() const noexcept -> auto { return this->push_front(List); } + [[nodiscard]] constexpr static auto push_front() noexcept -> auto + { + return list::push_front(List); + } template requires(N <= types_size) - [[nodiscard]] consteval auto pop_back() const noexcept -> auto + [[nodiscard]] constexpr static auto pop_back() noexcept -> auto { - (void)this; - if constexpr (N == 0) { return list{}; } else if constexpr (N == types_size) { return list<>{}; } else @@ -309,10 +306,8 @@ namespace gal::prometheus::functional template requires (N <= types_size) - [[nodiscard]] consteval auto pop_front() const noexcept -> auto + [[nodiscard]] constexpr static auto pop_front() noexcept -> auto { - (void)this; - if constexpr (N == 0) { return list{}; } else if constexpr (N == types_size) { return list<>{}; } else @@ -326,24 +321,28 @@ namespace gal::prometheus::functional template requires(N <= types_size) - [[nodiscard]] consteval auto back() const noexcept -> auto { return this->template pop_front(); } + [[nodiscard]] constexpr static auto back() noexcept -> auto + { + return list::pop_front(); + } template requires(N <= types_size) - [[nodiscard]] consteval auto front() const noexcept -> auto { return this->template pop_back(); } + [[nodiscard]] constexpr static auto front() noexcept -> auto + { + return list::pop_back(); + } template requires(N <= types_size) - [[nodiscard]] consteval auto unique_front() const noexcept -> auto + [[nodiscard]] constexpr static auto unique_front() noexcept -> auto { - (void)this; - if constexpr (N <= 1) { return list{}; } else { using type = typename unique_impl< - decltype(list{}.template front().template pop_front<1>()), - decltype(list{}.template front<1>()) + decltype(list::front().template pop_front<1>()), + decltype(list::front<1>()) >::type; if constexpr (N == types_size) // @@ -352,23 +351,21 @@ namespace gal::prometheus::functional } else // { - return type{}.template push_back()>(); + return type{}.template push_back()>(); } } } template requires(N <= types_size) - [[nodiscard]] consteval auto unique_back() const noexcept -> auto + [[nodiscard]] constexpr static auto unique_back() noexcept -> auto { - (void)this; - if constexpr (N <= 1) { return list{}; } else { using type = typename unique_impl< - decltype(list{}.template back().template pop_front<1>()), - decltype(list{}.template back().template front<1>()) + decltype(list::back().template pop_front<1>()), + decltype(list::back().template front<1>()) >::type; if constexpr (N == types_size) // @@ -377,52 +374,62 @@ namespace gal::prometheus::functional } else // { - return this->template front().template push_back(); + return list::front().template push_back(); } } } - [[nodiscard]] consteval auto unique() const noexcept -> auto { return this->template unique_back(); } + [[nodiscard]] constexpr static auto unique() noexcept -> auto + { + return list::unique_back(); + } - [[nodiscard]] consteval auto to_tuple() const noexcept -> std::tuple + [[nodiscard]] constexpr static auto to_tuple() noexcept -> std::tuple { - (void)this; return {}; } template typename Prediction> - [[nodiscard]] consteval auto sub_list() const noexcept -> typename sub_list_impl>::type + [[nodiscard]] constexpr static auto sub_list() noexcept -> typename sub_list_impl>::type { - (void)this; return {}; } template typename Prediction> - requires requires(list l) { l.sub_list(); } - [[nodiscard]] consteval auto sub_list() const noexcept -> auto + requires requires { list::sub_list(); } + [[nodiscard]] constexpr static auto sub_list() noexcept -> auto { - return this->sub_list(); + return list::sub_list(); } }; } - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN template - constexpr auto type_list = type_list_detail::list{}; + struct type_list_type : type_list_detail::list {}; - template - using type_list_type = std::decay_t; + template + constexpr auto type_list = type_list_type{}; + + template + struct is_type_list : type_list_detail::is_list {}; + + template + constexpr auto is_type_list_v = type_list_detail::is_list_v; template concept type_list_t = type_list_detail::list_t; + // template + // constexpr auto type_list> = type_list_type{}; + // + // template + // constexpr auto type_list> = type_list_type{}; + template [[nodiscard]] constexpr auto to_type_list(const std::tuple) noexcept -> auto { return type_list; } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -} // namespace gal::prometheus::functional +} #undef TYPE_LIST_WORKAROUND_BINDER diff --git a/src/functional/value_list.ixx b/src/functional/value_list.hpp similarity index 64% rename from src/functional/value_list.ixx rename to src/functional/value_list.hpp index c9a04735..eb8014a9 100644 --- a/src/functional/value_list.ixx +++ b/src/functional/value_list.hpp @@ -1,27 +1,15 @@ // This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal +// Copyright (C) 2022-2025 Life4gal // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.functional:value_list; - -import std; - -#else #pragma once #include -#include +#include #include -#endif - #if defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) or defined(GAL_PROMETHEUS_COMPILER_GNU) #define VALUE_LIST_WORKAROUND_BINDER(Value, Prediction) binder::template rebind #else @@ -156,182 +144,179 @@ namespace gal::prometheus::functional }; public: - [[nodiscard]] consteval auto size() const noexcept -> std::size_t + [[nodiscard]] constexpr static auto size() noexcept -> std::size_t { - (void)this; return values_size; } template typename Projection> - [[nodiscard]] consteval auto projection() const noexcept -> list::value...> // NOLINT(modernize-type-traits) + [[nodiscard]] constexpr static auto projection() noexcept -> list::value...> // NOLINT(modernize-type-traits) { - (void)this; return {}; } template typename Prediction> - [[nodiscard]] consteval auto all() const noexcept -> bool + [[nodiscard]] constexpr static auto all() noexcept -> bool { - (void)this; return ((Prediction::value) and ...); } template typename Prediction> - [[nodiscard]] consteval auto all() const noexcept -> bool + [[nodiscard]] constexpr static auto all() noexcept -> bool { - (void)this; - return this->template all(); + return list::all(); } template - [[nodiscard]] consteval auto all() const noexcept -> bool { return this->all(); } + [[nodiscard]] constexpr static auto all() noexcept -> bool + { + return list::all(); + } template typename Prediction> - [[nodiscard]] consteval auto any() const noexcept -> bool + [[nodiscard]] constexpr static auto any() noexcept -> bool { - (void)this; return ((Prediction::value) or ...); } template typename Prediction> - [[nodiscard]] consteval auto any() const noexcept -> bool // + [[nodiscard]] constexpr static auto any() noexcept -> bool // { - return this->template any(); + return list::any(); } template - [[nodiscard]] consteval auto any() const noexcept -> bool { return this->any(); } + [[nodiscard]] constexpr static auto any() noexcept -> bool + { + return list::any(); + } template requires((values_size != 0) and (Index <= values_size - 1)) - [[nodiscard]] consteval auto nth_value() const noexcept -> auto + [[nodiscard]] constexpr static auto nth_value() noexcept -> auto { - (void)this; return nth_value_impl::value; } template typename Prediction> - requires(static_cast(nullptr)->any()) - [[nodiscard]] consteval auto index_of() const noexcept -> std::size_t + requires(list::any()) + [[nodiscard]] constexpr static auto index_of() noexcept -> std::size_t { - (void)this; return index_of_impl<0, Prediction, Values...>::value; } template typename Prediction> - requires requires(list l) { l.template index_of(); } - [[nodiscard]] consteval auto index_of() const noexcept -> std::size_t // + requires requires { list::index_of(); } + [[nodiscard]] constexpr static auto index_of() noexcept -> std::size_t // { - return this->template index_of(); + return list::index_of(); } template - requires requires(list l) { l.template index_of(); } - [[nodiscard]] consteval auto index_of() const noexcept -> std::size_t // + requires requires { list::index_of(); } + [[nodiscard]] constexpr static auto index_of() noexcept -> std::size_t // { - return this->template index_of(); + return list::index_of(); } - [[nodiscard]] consteval auto reverse() const noexcept -> auto // + [[nodiscard]] constexpr static auto reverse() noexcept -> auto // { return [](std::index_sequence) noexcept -> auto // { - return list(nullptr)->template nth_value()...>{}; + return list()...>{}; }(std::make_index_sequence{}); } template - [[nodiscard]] consteval auto push_back() const noexcept -> list + [[nodiscard]] constexpr static auto push_back() noexcept -> list { - (void)this; return {}; } template - [[nodiscard]] consteval auto push_back(const list) const noexcept -> list // + [[nodiscard]] constexpr static auto push_back(const list) noexcept -> list // { - return this->template push_back(); + return list::push_back(); } template - [[nodiscard]] consteval auto push_back() const noexcept -> auto { return this->push_back(List); } + [[nodiscard]] constexpr static auto push_back() noexcept -> auto + { + return list::push_back(List); + } template - [[nodiscard]] consteval auto push_front() const noexcept -> list + [[nodiscard]] constexpr static auto push_front() noexcept -> list { - (void)this; return {}; } template - [[nodiscard]] consteval auto push_front(const list) const noexcept -> list // + [[nodiscard]] constexpr static auto push_front(const list) noexcept -> list // { - return this->template push_front(); + return list::push_front(); } template - [[nodiscard]] consteval auto push_front() const noexcept -> auto { return this->push_front(List); } + [[nodiscard]] constexpr static auto push_front() noexcept -> auto + { + return list::push_front(List); + } template requires(N <= values_size) - [[nodiscard]] consteval auto pop_back() const noexcept -> auto + [[nodiscard]] constexpr static auto pop_back() noexcept -> auto { - (void)this; - if constexpr (N == 0) { return list{}; } else if constexpr (N == values_size) { return list<>{}; } else { return [](std::index_sequence) noexcept -> auto // { - return list(nullptr)->template nth_value()...>{}; + return list()...>{}; }(std::make_index_sequence{}); } } template requires(N <= values_size) - [[nodiscard]] consteval auto pop_front() const noexcept -> auto + [[nodiscard]] constexpr static auto pop_front() noexcept -> auto { - (void)this; - if constexpr (N == 0) { return list{}; } else if constexpr (N == values_size) { return list<>{}; } else { return [](std::index_sequence) noexcept -> auto // { - return list(nullptr)->template nth_value...>{}; + return list...>{}; }(std::make_index_sequence{}); } } template requires(N <= values_size) - [[nodiscard]] consteval auto back() const noexcept -> auto // + [[nodiscard]] constexpr static auto back() noexcept -> auto // { - return this->template pop_front(); + return list::pop_front(); } template requires(N <= values_size) - [[nodiscard]] consteval auto front() const noexcept -> auto // + [[nodiscard]] constexpr static auto front() noexcept -> auto // { - return this->template pop_back(); + return list::pop_back(); } template requires(N <= values_size) - [[nodiscard]] consteval auto unique_front() const noexcept -> auto + [[nodiscard]] constexpr static auto unique_front() noexcept -> auto { - (void)this; - if constexpr (N <= 1) { return list{}; } else { using type = typename unique_impl< - decltype(static_cast(nullptr)->template front().template pop_front<1>()), - decltype(static_cast(nullptr)->template front<1>()) + decltype(list::front().template pop_front<1>()), + decltype(list::front<1>()) >::type; if constexpr (N == values_size) // @@ -340,23 +325,21 @@ namespace gal::prometheus::functional } else // { - return type{}.template push_back(nullptr)->template back()>(); + return type{}.template push_back()>(); } } } template requires(N <= values_size) - [[nodiscard]] consteval auto unique_back() const noexcept -> auto + [[nodiscard]] constexpr static auto unique_back() noexcept -> auto { - (void)this; - if constexpr (N <= 1) { return list{}; } else { using type = typename unique_impl< - decltype(static_cast(nullptr)->template back().template pop_front<1>()), - decltype(static_cast(nullptr)->template back().template front<1>()) + decltype(list::back().template pop_front<1>()), + decltype(list::back().template front<1>()) >::type; if constexpr (N == values_size) // @@ -365,25 +348,27 @@ namespace gal::prometheus::functional } else // { - return this->template front().template push_back(); + return list::front().template push_back(); } } } - [[nodiscard]] consteval auto unique() const noexcept -> auto { return this->template unique_back(); } + [[nodiscard]] constexpr static auto unique() noexcept -> auto + { + return list::unique_back(); + } template typename Prediction> - [[nodiscard]] consteval auto sub_list() const noexcept -> typename sub_list_impl>::type + [[nodiscard]] constexpr static auto sub_list() noexcept -> typename sub_list_impl>::type { - (void)this; return {}; } template typename Prediction> - requires requires(list l) { l.template sub_list(); } - [[nodiscard]] consteval auto sub_list() const noexcept -> auto // + requires requires { list::sub_list(); } + [[nodiscard]] constexpr static auto sub_list() noexcept -> auto // { - return this->template sub_list(); + return list::sub_list(); } }; @@ -407,10 +392,8 @@ namespace gal::prometheus::functional { template requires(((Cs >= '0' and Cs <= '9') or Cs == '\'') and ...) - [[nodiscard]] constexpr auto to_integral() const noexcept -> T + [[nodiscard]] constexpr static auto to_integral() noexcept -> T { - (void)this; - T result{0}; ( @@ -428,10 +411,8 @@ namespace gal::prometheus::functional template requires(((Cs >= '0' and Cs <= '9') or Cs == '\'' or Cs == '.') and ...) - [[nodiscard]] constexpr auto to_floating_point() const noexcept -> T + [[nodiscard]] constexpr static auto to_floating_point() noexcept -> T { - (void)this; - auto result = static_cast(0); auto fraction = static_cast(0.1); @@ -462,11 +443,9 @@ namespace gal::prometheus::functional return result; } - [[nodiscard]] constexpr auto numerator_length() const noexcept -> std::size_t // + [[nodiscard]] constexpr static auto numerator_length() noexcept -> std::size_t // requires(((Cs >= '0' and Cs <= '9') or Cs == '\'' or Cs == '.') and ...) { - (void)this; - std::size_t length = 0; bool found = false; @@ -481,11 +460,9 @@ namespace gal::prometheus::functional return length; } - [[nodiscard]] constexpr auto denominator_length() const noexcept -> std::size_t // + [[nodiscard]] constexpr static auto denominator_length() noexcept -> std::size_t // requires(((Cs >= '0' and Cs <= '9') or Cs == '\'' or Cs == '.') and ...) { - (void)this; - std::size_t length = 0; bool found = false; @@ -502,25 +479,33 @@ namespace gal::prometheus::functional }; } - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN template - constexpr auto value_list = value_list_detail::list{}; + struct value_list_type : value_list_detail::list {}; + + template + constexpr auto value_list = value_list_type{}; - template - using value_list_type = std::decay_t; + template + struct is_value_list : value_list_detail::is_list {}; + + template + constexpr auto is_value_list_v = value_list_detail::is_list_v; template concept value_list_t = value_list_detail::list_t; template - constexpr auto char_list = value_list_detail::char_list{}; + struct char_list_type : value_list_detail::char_list {}; - template - using char_list_type = std::decay_t; + template + constexpr auto char_list = char_list_type{}; + + template + struct is_char_list : value_list_detail::is_char_list {}; + + template + constexpr auto is_char_list_v = value_list_detail::is_char_list_v; template concept char_list_t = value_list_detail::char_list_t; - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END } - -#undef VALUE_LIST_WORKAROUND_BINDER diff --git a/src/gui/draw_list.ixx b/src/gui/draw_list.ixx deleted file mode 100644 index e8a065e1..00000000 --- a/src/gui/draw_list.ixx +++ /dev/null @@ -1,2742 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if not defined(GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG) -#if defined(DEBUG) or defined(_DEBUG) -#define GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG 1 -#else -#define GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG 0 -#endif -#endif - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include -#if __has_include() -#include -#endif -#if __has_include() -#include -#endif - -export module gal.prometheus.primitive:draw_list; - -import std; -import gal.prometheus.functional; -import gal.prometheus.primitive; -import gal.prometheus.chars; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -import :font; - -#else -#pragma once - -#if __has_include() -#include -#endif -#if __has_include() -#include -#endif - -#include -#include -#include -#include -#include - -#if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG -#include -#include -#include -#endif - -#include -#include -#include -#include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -#endif - -namespace gal::prometheus::gui -{ - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - - enum class DrawFlag : std::uint32_t - { - NONE = 0, - // specify that shape should be closed - // @see basic_draw_list::draw_polygon_line - // @see basic_draw_list::draw_polygon_line_aa - // @see basic_draw_list::path_stroke - CLOSED = 1 << 0, - // enable rounding left-top corner only (when rounding > 0.0f, we default to all corners) - // @see basic_draw_list::path_rect - // @see basic_draw_list::rect - // @see basic_draw_list::rect_filled - ROUND_CORNER_LEFT_TOP = 1 << 1, - // enable rounding right_top corner only (when rounding > 0.0f, we default to all corners) - // @see basic_draw_list::path_rect - // @see basic_draw_list::rect - // @see basic_draw_list::rect_filled - ROUND_CORNER_RIGHT_TOP = 1 << 2, - // enable rounding left-bottom corner only (when rounding > 0.0f, we default to all corners) - // @see basic_draw_list::path_rect - // @see basic_draw_list::rect - // @see basic_draw_list::rect_filled - ROUND_CORNER_LEFT_BOTTOM = 1 << 3, - // enable rounding right-bottom corner only (when rounding > 0.0f, we default to all corners) - // @see basic_draw_list::path_rect - // @see basic_draw_list::rect - // @see basic_draw_list::rect_filled - ROUND_CORNER_RIGHT_BOTTOM = 1 << 4, - // disable rounding on all corners (when rounding > 0.0f) - ROUND_CORNER_NONE = 1 << 5, - - ROUND_CORNER_LEFT = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_LEFT_BOTTOM, - ROUND_CORNER_TOP = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP, - ROUND_CORNER_RIGHT = ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_RIGHT_BOTTOM, - ROUND_CORNER_BOTTOM = ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, - - ROUND_CORNER_ALL = ROUND_CORNER_LEFT_TOP | ROUND_CORNER_RIGHT_TOP | ROUND_CORNER_LEFT_BOTTOM | ROUND_CORNER_RIGHT_BOTTOM, - ROUND_CORNER_DEFAULT = ROUND_CORNER_ALL, - ROUND_CORNER_MASK = ROUND_CORNER_ALL | ROUND_CORNER_NONE, - }; - - enum class DrawListFlag : std::uint32_t - { - NONE = 0x0000'0000, - ANTI_ALIASED_LINE = 0x0000'0001, - ANTI_ALIASED_LINE_USE_TEXTURE = 0x0000'0010, - ANTI_ALIASED_FILL = 0x0000'0100, - }; - - enum class ArcQuadrant : std::uint32_t - { - // [0~3) - Q1 = 0x0000'0001, - // [3~6) - Q2 = 0x0000'0010, - // [6~9) - Q3 = 0x0000'0100, - // [9~12) - Q4 = 0x0000'1000, - - RIGHT_TOP = Q1, - LEFT_TOP = Q2, - LEFT_BOTTOM = Q3, - RIGHT_BOTTOM = Q4, - TOP = Q1 | Q2, - BOTTOM = Q3 | Q4, - LEFT = Q2 | Q3, - RIGHT = Q1 | Q4, - ALL = Q1 | Q2 | Q3 | Q4, - - // [3, 0) - Q1_CLOCK_WISH = 0x0001'0000, - // [6, 3) - Q2_CLOCK_WISH = 0x0010'0000, - // [9, 6) - Q3_CLOCK_WISH = 0x0100'0000, - // [12, 9) - Q4_CLOCK_WISH = 0x1000'0000, - - RIGHT_TOP_CLOCK_WISH = Q1_CLOCK_WISH, - LEFT_TOP_CLOCK_WISH = Q2_CLOCK_WISH, - LEFT_BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH, - RIGHT_BOTTOM_CLOCK_WISH = Q4_CLOCK_WISH, - TOP_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH, - BOTTOM_CLOCK_WISH = Q3_CLOCK_WISH | Q4_CLOCK_WISH, - LEFT_CLOCK_WISH = Q2_CLOCK_WISH | Q3_CLOCK_WISH, - RIGHT_CLOCK_WISH = Q1_CLOCK_WISH | Q4_CLOCK_WISH, - ALL_CLOCK_WISH = Q1_CLOCK_WISH | Q2_CLOCK_WISH | Q3_CLOCK_WISH | Q4_CLOCK_WISH, - }; - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END - - namespace draw_list_detail - { - [[nodiscard]] constexpr auto to_fixed_rect_corner_flag(const DrawFlag flag) noexcept -> DrawFlag - { - if (functional::exclude(flag, DrawFlag::ROUND_CORNER_MASK)) - { - using functional::operators::operator|; - return DrawFlag::ROUND_CORNER_ALL | flag; - } - - return flag; - } - - [[nodiscard]] constexpr auto to_fixed_normal(const float x, const float y) noexcept -> std::pair - { - if (const auto d = functional::pow(x, 2) + functional::pow(y, 2); - d > 1e-6f) - { - // fixme - const auto inv_len = [d] - { - #if defined(__AVX512F__) - __m512 d_v = _mm512_set1_ps(d); - __m512 inv_len_v = _mm512_rcp14_ps(d_v); - return _mm512_cvtss_f32(inv_len_v); - #elif defined(__AVX__) - __m256 d_v = _mm256_set1_ps(d); - __m256 inv_len_v = _mm256_rcp_ps(d_v); - return _mm256_cvtss_f32(inv_len_v); - #elif defined(__SSE4_1__) or defined(__SSE3__) or defined(__SSE__) - __m128 d_v = _mm_set_ss(d); - __m128 inv_len_v = _mm_rcp_ss(d_v); - return _mm_cvtss_f32(inv_len_v); - #else - return 1.0f / d; - #endif - }(); - - return {x * inv_len, y * inv_len}; - } - - return {x, y}; - } - - using point_type = primitive::basic_point_2d; - using uv_type = primitive::basic_point_2d; - using color_type = primitive::basic_color; - - using extent_type = primitive::basic_extent_2d; - using circle_type = primitive::basic_circle_2d; - using ellipse_type = primitive::basic_ellipse_2d; - using rect_type = primitive::basic_rect_2d; - - using vertex_type = primitive::basic_vertex; - using index_type = std::uint16_t; - - // @see https://stackoverflow.com/a/2244088/15194693 - // Number of segments (N) is calculated using equation: - // N = ceil ( pi / acos(1 - error / r) ) where r > 0 and error <= r - constexpr std::uint32_t circle_segments_min = 4; - constexpr std::uint32_t circle_segments_max = 512; - constexpr auto circle_segments_calc = [](const float radius, const float max_error) noexcept -> auto - { - constexpr auto circle_segments_roundup_to_even = [](const auto v) noexcept -> auto - { - return (v + 1) / 2 * 2; - }; - - return std::ranges::clamp( - circle_segments_roundup_to_even(static_cast(std::ceil(std::numbers::pi_v / std::acos(1 - std::ranges::min(radius, max_error) / radius)))), - circle_segments_min, - circle_segments_max - ); - }; - constexpr auto circle_segments_calc_radius = [](const std::size_t n, const float max_error) noexcept -> auto - { - return max_error / (1 - std::cos(std::numbers::pi_v / std::ranges::max(static_cast(n), std::numbers::pi_v))); - }; - constexpr auto circle_segments_calc_error = [](const std::size_t n, const float radius) noexcept -> auto - { - return (1 - std::cos(std::numbers::pi_v / std::ranges::max(static_cast(n), std::numbers::pi_v))) / radius; - }; - - constexpr std::size_t vertex_sample_points_count = 48; - using vertex_sample_points_type = std::array; - constexpr std::size_t circle_segment_counts_count = 64; - using circle_segment_counts_type = std::array; - - constexpr auto vertex_sample_points = [](std::index_sequence) noexcept -> vertex_sample_points_type - { - constexpr auto make_point = []() noexcept -> point_type - { - const auto a = static_cast(I) / static_cast(vertex_sample_points_type{}.size()) * 2 * std::numbers::pi_v; - return {functional::cos(a), -functional::sin(a)}; - }; - - return {{make_point.template operator()()...}}; - }(std::make_index_sequence{}); - - constexpr auto range_of_quadrant = [](const ArcQuadrant quadrant) noexcept -> std::pair - { - constexpr auto factor = static_cast(vertex_sample_points.size() / 12); - - switch (quadrant) - { - case ArcQuadrant::Q1: { return std::make_pair(0 * factor, 3 * factor); } - case ArcQuadrant::Q2: { return std::make_pair(3 * factor, 6 * factor); } - case ArcQuadrant::Q3: { return std::make_pair(6 * factor, 9 * factor); } - case ArcQuadrant::Q4: { return std::make_pair(9 * factor, 12 * factor); } - case ArcQuadrant::TOP: { return std::make_pair(0 * factor, 6 * factor); } - case ArcQuadrant::BOTTOM: { return std::make_pair(6 * factor, 12 * factor); } - case ArcQuadrant::LEFT: { return std::make_pair(3 * factor, 9 * factor); } - case ArcQuadrant::RIGHT: { return std::make_pair(9 * factor, 15 * factor); } - case ArcQuadrant::ALL: { return std::make_pair(0 * factor, 12 * factor); } - case ArcQuadrant::Q1_CLOCK_WISH: { return std::make_pair(3 * factor, 0 * factor); } - case ArcQuadrant::Q2_CLOCK_WISH: { return std::make_pair(6 * factor, 3 * factor); } - case ArcQuadrant::Q3_CLOCK_WISH: { return std::make_pair(9 * factor, 6 * factor); } - case ArcQuadrant::Q4_CLOCK_WISH: { return std::make_pair(12 * factor, 9 * factor); } - case ArcQuadrant::TOP_CLOCK_WISH: { return std::make_pair(6 * factor, 0 * factor); } - case ArcQuadrant::BOTTOM_CLOCK_WISH: { return std::make_pair(12 * factor, 6 * factor); } - case ArcQuadrant::LEFT_CLOCK_WISH: { return std::make_pair(9 * factor, 3 * factor); } - case ArcQuadrant::RIGHT_CLOCK_WISH: { return std::make_pair(15 * factor, 9 * factor); } - case ArcQuadrant::ALL_CLOCK_WISH: { return std::make_pair(12 * factor, 0 * factor); } - } - - std::unreachable(); - }; - - constexpr auto bezier_cubic_calc = [](const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const float tolerance) noexcept -> point_type - { - const auto u = 1.f - tolerance; - - const auto w1 = functional::pow(u, 3); - const auto w2 = 3 * functional::pow(u, 2) * tolerance; - const auto w3 = 3 * u * functional::pow(tolerance, 2); - const auto w4 = functional::pow(tolerance, 3); - - return { - p1.x * w1 + p2.x * w2 + p3.x * w3 + p4.x * w4, - p1.y * w1 + p2.y * w2 + p3.y * w3 + p4.y * w4 - }; - }; - - constexpr auto bezier_quadratic_calc = [](const point_type& p1, const point_type& p2, const point_type& p3, const float tolerance) noexcept -> point_type - { - const auto u = 1.f - tolerance; - - const auto w1 = functional::pow(u, 2); - const auto w2 = 2 * u * tolerance; - const auto w3 = functional::pow(tolerance, 2); - - return { - p1.x * w1 + p2.x * w2 + p3.x * w3, - p1.y * w1 + p2.y * w2 + p3.y * w3 - }; - }; - - constexpr auto draw_list_texture_line_max_width{63}; - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - - class DrawListSharedData final - { - public: - using vertex_type = draw_list_detail::vertex_type; - using uv_type = vertex_type::uv_type; - - using circle_segment_counts_type = draw_list_detail::circle_segment_counts_type; - using vertex_sample_points_type = draw_list_detail::vertex_sample_points_type; - - constexpr static auto vertex_sample_points_count = draw_list_detail::vertex_sample_points_count; - - private: - circle_segment_counts_type circle_segment_counts_; - // Maximum error (in pixels) allowed when using `circle`/`circle_filled` or drawing rounded corner rectangles with no explicit segment count specified. - // Decrease for higher quality but more geometry. - float circle_segment_max_error_; - // Cutoff radius after which arc drawing will fall back to slower `path_arc` - float arc_fast_radius_cutoff_; - // Tessellation tolerance when using `path_bezier_curve` without a specific number of segments. - // Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. - float curve_tessellation_tolerance_; - - uv_type texture_uv_of_white_pixel_; - - font_type default_font_; - - public: - DrawListSharedData() noexcept - : - circle_segment_counts_{}, - circle_segment_max_error_{}, - arc_fast_radius_cutoff_{}, - curve_tessellation_tolerance_{1.25f}, - texture_uv_of_white_pixel_{0}, - default_font_{} - { - set_circle_tessellation_max_error(.3f); - } - - constexpr auto set_circle_tessellation_max_error(const float max_error) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(max_error > .0f); - - if (circle_segment_max_error_ == max_error) // NOLINT(clang-diagnostic-float-equal) - { - return; - } - - for (decltype(circle_segment_counts_.size()) i = 0; i < circle_segment_counts_.size(); ++i) - { - const auto radius = static_cast(i); - circle_segment_counts_[i] = static_cast(draw_list_detail::circle_segments_calc(radius, max_error)); - } - circle_segment_max_error_ = max_error; - arc_fast_radius_cutoff_ = draw_list_detail::circle_segments_calc_radius(vertex_sample_points_count, max_error); - } - - [[nodiscard]] constexpr auto get_circle_tessellation_max_error() const noexcept -> float - { - return circle_segment_max_error_; - } - - [[nodiscard]] constexpr auto get_arc_fast_radius_cutoff() const noexcept -> float - { - return arc_fast_radius_cutoff_; - } - - constexpr auto set_curve_tessellation_tolerance(const float tolerance) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(tolerance > .0f); - - curve_tessellation_tolerance_ = tolerance; - } - - [[nodiscard]] constexpr auto get_curve_tessellation_tolerance() const noexcept -> float - { - return curve_tessellation_tolerance_; - } - - [[nodiscard]] constexpr auto get_circle_auto_segment_count(const float radius) const noexcept -> auto - { - // ceil to never reduce accuracy - if (const auto radius_index = static_cast(radius + .999999f); radius_index < circle_segment_counts_.size()) - { - return circle_segment_counts_[radius_index]; - } - return static_cast(draw_list_detail::circle_segments_calc(radius, circle_segment_max_error_)); - } - - constexpr auto set_texture_uv_of_white_pixel(const uv_type& uv) noexcept -> void - { - texture_uv_of_white_pixel_ = uv; - } - - [[nodiscard]] constexpr auto get_texture_uv_of_white_pixel() const noexcept -> const uv_type& - { - return texture_uv_of_white_pixel_; - } - - auto set_default_font(font_type&& font) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font.texture_data); - - default_font_ = std::move(font); - } - - [[nodiscard]] constexpr auto get_default_font() noexcept -> font_type& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(default_font_.texture_data); - - return default_font_; - } - - [[nodiscard]] constexpr auto get_default_font() const noexcept -> const font_type& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(default_font_.texture_data); - - return default_font_; - } - }; - - class DrawList final - { - public: - using point_type = draw_list_detail::point_type; - using uv_type = draw_list_detail::uv_type; - using color_type = draw_list_detail::color_type; - - using extent_type = draw_list_detail::extent_type; - using circle_type = draw_list_detail::circle_type; - using ellipse_type = draw_list_detail::ellipse_type; - using rect_type = draw_list_detail::rect_type; - - using texture_id_type = font_type::texture_id_type; - - using vertex_type = draw_list_detail::vertex_type; - using index_type = draw_list_detail::index_type; - - template - using list_type = std::vector; - - using size_type = std::size_t; - - struct command_type - { - rect_type clip_rect; - texture_id_type texture_id; - // there may be additional data - - // ======================= - - // set by index_list.size() - // start offset in `index_list` - size_type index_offset; - // set by subsequent draw_xxx - // number of indices (multiple of 3) to be rendered as triangles - size_type element_count; - }; - - using command_list_type = list_type; - using vertex_list_type = list_type; - using index_list_type = list_type; - using path_list_type = list_type; - - private: - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - using debug_vertex_range_type = std::pair; - using debug_index_range_type = std::pair; - - using debug_vertex_list_type = std::span; - using debug_index_list_type = std::span; - - struct debug_range_info_type - { - debug_vertex_range_type vertices; - debug_index_range_type indices; - }; - - struct debug_list_info_type - { - std::string what; - - debug_vertex_list_type vertices; - debug_index_list_type indices; - }; - - using debug_range_info_list_type = list_type; - using debug_list_info_list_type = list_type; - #endif - - DrawListFlag draw_list_flag_; - std::shared_ptr shared_data_; - - // vertex_list: v1-v2-v3-v4 + v5-v6-v7-v8 + v9-v10-v11 => rect0 + rect1(clipped by rect0) + triangle0(clipped by rect1) - // index_list: 0/1/2-0/2/3 + 4/5/6-4/6/7 + 8/9/10 - // command_list: - // 0: .clip_rect = {0, 0, root_window_width, root_window_height}, .index_offset = 0, .element_count = root_window_element_count + 6 (two triangles => 0/1/2-0/2/3) - // 1: .clip_rect = {max(rect0.left, rect1.left), max(rect0.top, rect1.top), min(rect0.right, rect1.right), min(rect0.bottom, rect1.bottom)}, .index_offset = root_window_element_count + 6, .element_count = 6 (two triangles => 4/5/6-4/6/7) - // 2: .clip_rect = {...}, .index_offset = root_window_element_count + 12, .element_count = 3 (one triangle => 8/9/10) - command_list_type command_list_; - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - list_type command_message_; - #endif - vertex_list_type vertex_list_; - index_list_type index_list_; - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_type debug_range_info_list_; - // If you want to get the exact data (and not just a [begin, end] of range), - // you have to `explicitly` call `bind_debug_info` after `all` operations are finished, - // because only then can you be sure that the data will be valid. - debug_list_info_list_type debug_list_info_list_; - #endif - - rect_type this_command_clip_rect_; - texture_id_type this_command_texture_id_; - // there may be additional data - - path_list_type path_list_; - - constexpr auto push_command( - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - std::string&& message - #endif - ) noexcept -> void - { - // fixme: If the window boundary is smaller than the rect boundary, the rect will no longer be valid. - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not this_command_clip_rect_.empty() and this_command_clip_rect_.valid()); - - command_list_.emplace_back( - command_type - { - .clip_rect = this_command_clip_rect_, - .texture_id = this_command_texture_id_, - .index_offset = index_list_.size(), - // set by subsequent draw_xxx - .element_count = 0 - } - ); - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - command_message_.emplace_back(std::move(message)); - #endif - } - - enum class ChangedElement - { - CLIP_RECT, - TEXTURE_ID, - }; - - template - constexpr auto on_element_changed() noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list_.empty()); - - const auto& [current_clip_rect, current_texture_id, current_index_offset, current_element_count] = command_list_.back(); - if (current_element_count != 0) - { - if constexpr (Element == ChangedElement::CLIP_RECT) - { - if (current_clip_rect != this_command_clip_rect_) - { - push_command( - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - std::format("[PUSH CLIP_RECT] {} -> {}", current_clip_rect, this_command_clip_rect_) - #endif - ); - - return; - } - } - else if constexpr (Element == ChangedElement::TEXTURE_ID) - { - if (current_texture_id != this_command_texture_id_) - { - push_command( - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - std::format("[PUSH TEXTURE_ID] [{}] -> [{}]", current_texture_id, this_command_texture_id_) - #endif - ); - - return; - } - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - } - - // try to merge with previous command if it matches, else use current command - if (command_list_.size() > 1) - { - if ( - const auto& [previous_clip_rect, previous_texture, previous_index_offset, previous_element_count] = command_list_[command_list_.size() - 2]; - current_element_count == 0 and - ( - this_command_clip_rect_ == previous_clip_rect and - this_command_texture_id_ == previous_texture - // there may be additional data - ) and - // sequential - current_index_offset == previous_index_offset + previous_element_count - ) - { - command_list_.pop_back(); - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - command_message_.pop_back(); - #endif - return; - } - } - - if constexpr (Element == ChangedElement::CLIP_RECT) - { - command_list_.back().clip_rect = this_command_clip_rect_; - } - else if constexpr (Element == ChangedElement::TEXTURE_ID) - { - command_list_.back().texture_id = this_command_texture_id_; - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - } - - constexpr auto draw_polygon_line(const color_type& color, const DrawFlag draw_flag, const float thickness) noexcept -> void - { - const auto path_point_count = path_list_.size(); - const auto& path_point = path_list_; - - if (path_point_count < 2 or color.alpha == 0) - { - return; - } - - const auto is_closed = not functional::exclude(draw_flag, DrawFlag::CLOSED); - const auto segments_count = is_closed ? path_point_count : path_point_count - 1; - - const auto vertex_count = segments_count * 4; - const auto index_count = segments_count * 6; - vertex_list_.reserve(vertex_list_.size() + vertex_count); - index_list_.reserve(index_list_.size() + index_count); - - command_list_.back().element_count += index_count; - - for (std::decay_t i = 0; i < segments_count; ++i) - { - const auto n = (i + 1) % path_point_count; - - const auto& p1 = path_point[i]; - const auto& p2 = path_point[n]; - - auto [normalized_x, normalized_y] = functional::normalize(p2.x - p1.x, p2.y - p1.y); - normalized_x *= (thickness * .5f); - normalized_y *= (thickness * .5f); - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(vertex_list_.size() + 3 <= std::numeric_limits::max()); - - const auto current_vertex_index = static_cast(vertex_list_.size()); - - const auto& opaque_uv = shared_data_->get_texture_uv_of_white_pixel(); - - vertex_list_.emplace_back(p1 + point_type{normalized_y, -normalized_x}, opaque_uv, color); - vertex_list_.emplace_back(p2 + point_type{normalized_y, -normalized_x}, opaque_uv, color); - vertex_list_.emplace_back(p2 + point_type{-normalized_y, normalized_x}, opaque_uv, color); - vertex_list_.emplace_back(p1 + point_type{-normalized_y, normalized_x}, opaque_uv, color); - - index_list_.push_back(current_vertex_index + 0); - index_list_.push_back(current_vertex_index + 1); - index_list_.push_back(current_vertex_index + 2); - index_list_.push_back(current_vertex_index + 0); - index_list_.push_back(current_vertex_index + 2); - index_list_.push_back(current_vertex_index + 3); - } - } - - constexpr auto draw_polygon_line_aa(const color_type& color, const DrawFlag draw_flag, float thickness) noexcept -> void - { - const auto path_point_count = path_list_.size(); - const auto& path_point = path_list_; - - if (path_point_count < 2 or color.alpha == 0) - { - return; - } - - const auto& opaque_uv = shared_data_->get_texture_uv_of_white_pixel(); - const auto transparent_color = color.transparent(); - - const auto is_closed = not functional::exclude(draw_flag, DrawFlag::CLOSED); - const auto segments_count = is_closed ? path_point_count : path_point_count - 1; - const auto is_thick_line = thickness > 1.f; - - thickness = std::ranges::max(thickness, 1.f); - const auto thickness_integer = static_cast(thickness); - const auto thickness_fractional = thickness - static_cast(thickness_integer); - - const auto is_use_texture = - ( - functional::contains(draw_list_flag_, DrawListFlag::ANTI_ALIASED_LINE_USE_TEXTURE) and - (thickness_integer < draw_list_detail::draw_list_texture_line_max_width) and - (thickness_fractional <= .00001f)); - - const auto vertex_cont = is_use_texture ? (path_point_count * 2) : (is_thick_line ? path_point_count * 4 : path_point_count * 3); - const auto index_count = is_use_texture ? (segments_count * 6) : (is_thick_line ? segments_count * 18 : segments_count * 12); - vertex_list_.reserve(vertex_list_.size() + vertex_cont); - index_list_.reserve(index_list_.size() + index_count); - - command_list_.back().element_count += index_count; - - // The first items are normals at each line point, then after that there are either 2 or 4 temp points for each line point - list_type temp_buffer{}; - temp_buffer.resize(path_point_count * ((is_use_texture or not is_thick_line) ? 3 : 5)); - auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; - auto temp_buffer_points = std::span{temp_buffer.begin() + static_cast(path_point_count), temp_buffer.end()}; - - // Calculate normals (tangents) for each line segment - for (std::decay_t i = 0; i < segments_count; ++i) - { - const auto n = (i + 1) % path_point_count; - const auto d = path_point[n] - path_point[i]; - - const auto [normalized_x, normalized_y] = functional::normalize(d.x, d.y); - temp_buffer_normals[i].x = normalized_y; - temp_buffer_normals[i].y = -normalized_x; - } - - if (not is_closed) - { - temp_buffer_normals[temp_buffer_normals.size() - 1] = temp_buffer_normals[temp_buffer_normals.size() - 2]; - } - - // If we are drawing a one-pixel-wide line without a texture, or a textured line of any width, we only need 2 or 3 vertices per point - if (is_use_texture or not is_thick_line) - { - // [PATH 1] Texture-based lines (thick or non-thick) - - // The width of the geometry we need to draw - this is essentially pixels for the line itself, plus "one pixel" for AA. - const auto half_draw_size = is_use_texture ? ((thickness * .5f) + 1) : 1.f; - - // If line is not closed, the first and last points need to be generated differently as there are no normals to blend - if (not is_closed) - { - temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * half_draw_size; - temp_buffer_points[1] = path_point[0] - temp_buffer_normals[0] * half_draw_size; - temp_buffer_points[(path_point_count - 1) * 2 + 0] = path_point[path_point_count - 1] + temp_buffer_normals[path_point_count - 1] * half_draw_size; - temp_buffer_points[(path_point_count - 1) * 2 + 1] = path_point[path_point_count - 1] - temp_buffer_normals[path_point_count - 1] * half_draw_size; - } - - const auto current_vertex_index = static_cast(vertex_list_.size()); - - // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges - // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) - auto vertex_index_for_start = current_vertex_index; - for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) - { - const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; - const auto vertex_index_for_end = static_cast( - // closed - (first_point_of_segment + 1) == path_point_count - ? current_vertex_index - : (vertex_index_for_start + (is_use_texture ? 2 : 3)) - ); - - // Average normals - const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; - // dm_x, dm_y are offset to the outer edge of the AA area - auto [dm_x, dm_y] = draw_list_detail::to_fixed_normal(d.x, d.y); - dm_x *= half_draw_size; - dm_y *= half_draw_size; - - // Add temporary vertexes for the outer edges - temp_buffer_points[second_point_of_segment * 2 + 0] = path_point[second_point_of_segment] + point_type{dm_x, dm_y}; - temp_buffer_points[second_point_of_segment * 2 + 1] = path_point[second_point_of_segment] - point_type{dm_x, dm_y}; - - if (is_use_texture) - { - // Add indices for two triangles - - // right - index_list_.push_back(vertex_index_for_end + 0); - index_list_.push_back(vertex_index_for_start + 0); - index_list_.push_back(vertex_index_for_start + 1); - // left - index_list_.push_back(vertex_index_for_end + 1); - index_list_.push_back(vertex_index_for_start + 1); - index_list_.push_back(vertex_index_for_end + 0); - } - else - { - // Add indexes for four triangles - - // right 1 - index_list_.push_back(vertex_index_for_end + 0); - index_list_.push_back(vertex_index_for_start + 0); - index_list_.push_back(vertex_index_for_start + 2); - // right 2 - index_list_.push_back(vertex_index_for_start + 2); - index_list_.push_back(vertex_index_for_end + 2); - index_list_.push_back(vertex_index_for_end + 0); - // left 1 - index_list_.push_back(vertex_index_for_end + 1); - index_list_.push_back(vertex_index_for_start + 1); - index_list_.push_back(vertex_index_for_start + 0); - // left 2 - index_list_.push_back(vertex_index_for_start + 0); - index_list_.push_back(vertex_index_for_end + 0); - index_list_.push_back(vertex_index_for_end + 1); - } - - vertex_index_for_start = vertex_index_for_end; - } - - // Add vertexes for each point on the line - if (is_use_texture) - { - // todo: get the texture - GAL_PROMETHEUS_ERROR_DEBUG_UNREACHABLE(); - } - else - { - // If we're not using a texture, we need the center vertex as well - for (std::decay_t i = 0; i < path_point_count; ++i) - { - // center of line - vertex_list_.emplace_back(path_point[i], opaque_uv, color); - // left-side outer edge - vertex_list_.emplace_back(temp_buffer_points[i * 2 + 0], opaque_uv, transparent_color); - // right-side outer edge - vertex_list_.emplace_back(temp_buffer_points[i * 2 + 1], opaque_uv, transparent_color); - } - } - } - else - { - // [PATH 2] Non-texture-based lines (non-thick) - - // we need to draw the solid line core and thus require four vertices per point - const auto half_inner_thickness = (thickness - 1.f) * .5f; - - // If line is not closed, the first and last points need to be generated differently as there are no normals to blend - if (not is_closed) - { - const auto point_last = path_point_count - 1; - temp_buffer_points[0] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 1.f); - temp_buffer_points[1] = path_point[0] + temp_buffer_normals[0] * (half_inner_thickness + 0.f); - temp_buffer_points[2] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 0.f); - temp_buffer_points[3] = path_point[0] - temp_buffer_normals[0] * (half_inner_thickness + 1.f); - temp_buffer_points[point_last * 4 + 0] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); - temp_buffer_points[point_last * 4 + 1] = path_point[point_last] + temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); - temp_buffer_points[point_last * 4 + 2] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 0.f); - temp_buffer_points[point_last * 4 + 3] = path_point[point_last] - temp_buffer_normals[point_last] * (half_inner_thickness + 1.f); - } - - const auto current_vertex_index = static_cast(vertex_list_.size()); - - // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges - // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) - auto vertex_index_for_start = current_vertex_index; - for (std::decay_t first_point_of_segment = 0; first_point_of_segment < segments_count; ++first_point_of_segment) - { - const auto second_point_of_segment = (first_point_of_segment + 1) % path_point_count; - const auto vertex_index_for_end = static_cast( - (first_point_of_segment + 1) == path_point_count - ? current_vertex_index - : (vertex_index_for_start + 4) - ); - - // Average normals - const auto d = (temp_buffer_normals[first_point_of_segment] + temp_buffer_normals[second_point_of_segment]) * .5f; - const auto [dm_x, dm_y] = draw_list_detail::to_fixed_normal(d.x, d.y); - const auto dm_out_x = dm_x * (half_inner_thickness + 1.f); - const auto dm_out_y = dm_y * (half_inner_thickness + 1.f); - const auto dm_in_x = dm_x * (half_inner_thickness + 0.f); - const auto dm_in_y = dm_y * (half_inner_thickness + 0.f); - - // Add temporary vertices - temp_buffer_points[second_point_of_segment * 4 + 0] = path_point[second_point_of_segment] + point_type{dm_out_x, dm_out_y}; - temp_buffer_points[second_point_of_segment * 4 + 1] = path_point[second_point_of_segment] + point_type{dm_in_x, dm_in_y}; - temp_buffer_points[second_point_of_segment * 4 + 2] = path_point[second_point_of_segment] - point_type{dm_in_x, dm_in_y}; - temp_buffer_points[second_point_of_segment * 4 + 3] = path_point[second_point_of_segment] - point_type{dm_out_x, dm_out_y}; - - // Add indexes - index_list_.push_back(vertex_index_for_end + 1); - index_list_.push_back(vertex_index_for_end + 1); - index_list_.push_back(vertex_index_for_start + 2); - - index_list_.push_back(vertex_index_for_start + 2); - index_list_.push_back(vertex_index_for_end + 2); - index_list_.push_back(vertex_index_for_end + 1); - - index_list_.push_back(vertex_index_for_end + 1); - index_list_.push_back(vertex_index_for_start + 1); - index_list_.push_back(vertex_index_for_start + 0); - - index_list_.push_back(vertex_index_for_start + 0); - index_list_.push_back(vertex_index_for_end + 0); - index_list_.push_back(vertex_index_for_end + 1); - - index_list_.push_back(vertex_index_for_end + 2); - index_list_.push_back(vertex_index_for_start + 2); - index_list_.push_back(vertex_index_for_start + 3); - - index_list_.push_back(vertex_index_for_start + 3); - index_list_.push_back(vertex_index_for_end + 3); - index_list_.push_back(vertex_index_for_end + 2); - - vertex_index_for_start = vertex_index_for_end; - } - - // Add vertices - for (std::decay_t i = 0; i < path_point_count; ++i) - { - vertex_list_.emplace_back(temp_buffer_points[i * 4 + 0], opaque_uv, transparent_color); - vertex_list_.emplace_back(temp_buffer_points[i * 4 + 1], opaque_uv, color); - vertex_list_.emplace_back(temp_buffer_points[i * 4 + 2], opaque_uv, color); - vertex_list_.emplace_back(temp_buffer_points[i * 4 + 2], opaque_uv, transparent_color); - } - } - } - - constexpr auto draw_convex_polygon_line_filled(const color_type& color) noexcept -> void - { - const auto path_point_count = path_list_.size(); - const auto& path_point = path_list_; - - if (path_point_count < 3 or color.alpha == 0) - { - return; - } - - const auto vertex_count = path_point_count; - const auto index_count = (path_point_count - 2) * 3; - vertex_list_.reserve(vertex_list_.size() + vertex_count); - index_list_.reserve(index_list_.size() + index_count); - - command_list_.back().element_count += index_count; - - const auto current_vertex_index = vertex_list_.size(); - - const auto& opaque_uv = shared_data_->get_texture_uv_of_white_pixel(); - - std::ranges::transform(path_point, std::back_inserter(vertex_list_), [opaque_uv, color](const point_type& point) noexcept -> vertex_type { return {point, opaque_uv, color}; }); - for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + 0 <= std::numeric_limits::max()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i - 1 <= std::numeric_limits::max()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_index + i <= std::numeric_limits::max()); - - index_list_.push_back(static_cast(current_vertex_index + 0)); - index_list_.push_back(static_cast(current_vertex_index + i - 1)); - index_list_.push_back(static_cast(current_vertex_index + i)); - } - } - - constexpr auto draw_convex_polygon_line_filled_aa(const color_type& color) noexcept -> void - { - const auto path_point_count = path_list_.size(); - const auto& path_point = path_list_; - - if (path_point_count < 3 or color.alpha == 0) - { - return; - } - - const auto& opaque_uv = shared_data_->get_texture_uv_of_white_pixel(); - const auto transparent_color = color.transparent(); - - const auto vertex_count = path_point_count * 2; - const auto index_count = (path_point_count - 2) * 3 + path_point_count * 6; - vertex_list_.reserve(vertex_list_.size() + vertex_count); - index_list_.reserve(index_list_.size() + index_count); - - command_list_.back().element_count += index_count; - - const auto current_vertex_inner_index = vertex_list_.size(); - const auto current_vertex_outer_index = vertex_list_.size() + 1; - - // Add indexes for fill - for (index_type i = 2; std::cmp_less(i, path_point_count); ++i) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + 0 <= std::numeric_limits::max()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + ((i - 1) << 1) <= std::numeric_limits::max()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + (i << 1) <= std::numeric_limits::max()); - - index_list_.push_back(static_cast(current_vertex_inner_index + 0)); - index_list_.push_back(static_cast(current_vertex_inner_index + ((i - 1) << 1))); - index_list_.push_back(static_cast(current_vertex_inner_index + (i << 1))); - } - - list_type temp_buffer{}; - temp_buffer.resize(path_point_count); - auto temp_buffer_normals = std::span{temp_buffer.begin(), path_point_count}; - - for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) - { - const auto d = path_point[n] - path_point[i]; - - const auto [normalized_x, normalized_y] = functional::normalize(d.x, d.y); - temp_buffer_normals[i].x = normalized_y; - temp_buffer_normals[i].y = -normalized_x; - } - for (auto i = path_point_count - 1, n = static_cast(0); n < path_point_count; i = n++) - { - // Average normals - const auto d = (temp_buffer_normals[n] + temp_buffer_normals[i]) * .5f; - auto [dm_x, dm_y] = draw_list_detail::to_fixed_normal(d.x, d.y); - dm_x *= .5f; - dm_y *= .5f; - - // inner - vertex_list_.emplace_back(path_point[n] - point_type{dm_x, dm_y}, opaque_uv, color); - // outer - vertex_list_.emplace_back(path_point[n] + point_type{dm_x, dm_y}, opaque_uv, transparent_color); - - // Add indexes for fringes - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + (n << 1) <= std::numeric_limits::max()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + (i << 1) <= std::numeric_limits::max()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + (i << 1) <=std::numeric_limits::max()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + (i << 1) <= std::numeric_limits::max()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_outer_index + (n << 1) <= std::numeric_limits::max()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_vertex_inner_index + (n << 1) <= std::numeric_limits::max()); - - index_list_.push_back(static_cast(current_vertex_inner_index + (n << 1))); - index_list_.push_back(static_cast(current_vertex_inner_index + (i << 1))); - index_list_.push_back(static_cast(current_vertex_outer_index + (i << 1))); - index_list_.push_back(static_cast(current_vertex_outer_index + (i << 1))); - index_list_.push_back(static_cast(current_vertex_outer_index + (n << 1))); - index_list_.push_back(static_cast(current_vertex_inner_index + (n << 1))); - } - } - - constexpr auto draw_rect_filled( - const rect_type& rect, - const color_type& color_left_top, - const color_type& color_right_top, - const color_type& color_left_bottom, - const color_type& color_right_bottom - ) noexcept -> void - { - // two triangle without path - constexpr auto vertex_count = 4; - constexpr auto index_count = 6; - vertex_list_.reserve(vertex_list_.size() + vertex_count); - index_list_.reserve(index_list_.size() + index_count); - - command_list_.back().element_count += index_count; - - const auto& opaque_uv = shared_data_->get_texture_uv_of_white_pixel(); - - const auto current_vertex_index = static_cast(vertex_list_.size()); - - vertex_list_.emplace_back(rect.left_top(), opaque_uv, color_left_top); - vertex_list_.emplace_back(rect.right_top(), opaque_uv, color_right_top); - vertex_list_.emplace_back(rect.right_bottom(), opaque_uv, color_right_bottom); - vertex_list_.emplace_back(rect.left_bottom(), opaque_uv, color_left_bottom); - - index_list_.push_back(current_vertex_index + 0); - index_list_.push_back(current_vertex_index + 1); - index_list_.push_back(current_vertex_index + 2); - index_list_.push_back(current_vertex_index + 0); - index_list_.push_back(current_vertex_index + 2); - index_list_.push_back(current_vertex_index + 3); - } - - constexpr auto draw_text( - const font_type& font, - const float font_size, - const point_type& p, - const color_type& color, - const std::string_view text, - const float wrap_width - ) noexcept -> void - { - const auto new_texture = this_command_texture_id_ != font.texture_id; - - if (new_texture) - { - push_texture_id(font.texture_id); - } - - const auto utf32_text = chars::convert(text); - - const auto vertex_count = 4 * utf32_text.size(); - const auto index_count = 6 * utf32_text.size(); - vertex_list_.reserve(vertex_list_.size() + vertex_count); - index_list_.reserve(index_list_.size() + index_count); - - command_list_.back().element_count += index_count; - - const float scale = font_size / font.pixel_height; - - auto it_input_current = utf32_text.begin(); - const auto it_input_end = utf32_text.end(); - - auto cursor = p + point_type{0, font_size}; - - while (it_input_current != it_input_end) - { - const auto c = *it_input_current; - it_input_current += 1; - - if (c == U'\n' or (wrap_width > 0 and cursor.x - p.x > wrap_width)) - { - cursor.x = p.x; - cursor.y += font.pixel_height * scale; - if (c == U'\n') - { - command_list_.back().element_count -= 6; - continue; - } - } - - const auto& [glyph_rect, glyph_uv, glyph_advance_x] = [&] - { - if (const auto it = font.glyphs.find(c); - it != font.glyphs.end()) - { - return it->second; - } - - return font.fallback_glyph; - }(); - - const auto advance_x = glyph_advance_x * scale; - const rect_type char_rect{ - cursor + point_type{static_cast(glyph_rect.left_top().x), -static_cast(glyph_rect.left_top().y)} * scale, - static_cast(glyph_rect.size()) * scale - }; - cursor.x += advance_x; - - const auto current_vertex_index = static_cast(vertex_list_.size()); - - vertex_list_.emplace_back(char_rect.left_top(), glyph_uv.left_top(), color); - vertex_list_.emplace_back(char_rect.right_top(), glyph_uv.right_top(), color); - vertex_list_.emplace_back(char_rect.right_bottom(), glyph_uv.right_bottom(), color); - vertex_list_.emplace_back(char_rect.left_bottom(), glyph_uv.left_bottom(), color); - - index_list_.push_back(current_vertex_index + 0); - index_list_.push_back(current_vertex_index + 1); - index_list_.push_back(current_vertex_index + 2); - index_list_.push_back(current_vertex_index + 0); - index_list_.push_back(current_vertex_index + 2); - index_list_.push_back(current_vertex_index + 3); - } - - if (new_texture) - { - pop_texture_id(); - } - } - - constexpr auto draw_image( - const texture_id_type texture_id, - const point_type& display_p1, - const point_type& display_p2, - const point_type& display_p3, - const point_type& display_p4, - const uv_type& uv_p1, - const uv_type& uv_p2, - const uv_type& uv_p3, - const uv_type& uv_p4, - const color_type& color - ) noexcept -> void - { - const auto new_texture = this_command_texture_id_ != texture_id; - - if (new_texture) - { - push_texture_id(texture_id); - } - - // two triangle without path - constexpr auto vertex_count = 4; - constexpr auto index_count = 6; - vertex_list_.reserve(vertex_list_.size() + vertex_count); - index_list_.reserve(index_list_.size() + index_count); - - command_list_.back().element_count += index_count; - - const auto current_vertex_index = static_cast(vertex_list_.size()); - - vertex_list_.emplace_back(display_p1, uv_p1, color); - vertex_list_.emplace_back(display_p2, uv_p2, color); - vertex_list_.emplace_back(display_p3, uv_p3, color); - vertex_list_.emplace_back(display_p4, uv_p4, color); - - index_list_.push_back(current_vertex_index + 0); - index_list_.push_back(current_vertex_index + 1); - index_list_.push_back(current_vertex_index + 2); - index_list_.push_back(current_vertex_index + 0); - index_list_.push_back(current_vertex_index + 2); - index_list_.push_back(current_vertex_index + 3); - - if (new_texture) - { - pop_texture_id(); - } - } - - constexpr auto draw_image_rounded( - const texture_id_type texture_id, - const rect_type& display_rect, - const rect_type& uv_rect, - const color_type& color, - float rounding, - DrawFlag flag - ) noexcept -> void - { - // @see `path_rect` - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(display_rect.valid() and not display_rect.empty()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(uv_rect.valid() and not uv_rect.empty()); - - if (rounding >= .5f) - { - flag = draw_list_detail::to_fixed_rect_corner_flag(flag); - - const auto v = functional::contains(flag, DrawFlag::ROUND_CORNER_TOP, DrawFlag::ROUND_CORNER_BOTTOM); - const auto h = functional::contains(flag, DrawFlag::ROUND_CORNER_LEFT, DrawFlag::ROUND_CORNER_RIGHT); - - rounding = std::ranges::min(rounding, display_rect.width() * (v ? .5f : 1.f) - 1.f); - rounding = std::ranges::min(rounding, display_rect.height() * (h ? .5f : 1.f) - 1.f); - } - - using functional::operators::operator&; - if (rounding < .5f or (DrawFlag::ROUND_CORNER_MASK & flag) == DrawFlag::ROUND_CORNER_NONE) - { - draw_image( - texture_id, - display_rect.left_top(), - display_rect.right_top(), - display_rect.right_bottom(), - display_rect.left_bottom(), - uv_rect.left_top(), - uv_rect.right_top(), - uv_rect.right_bottom(), - uv_rect.left_bottom(), - color - ); - } - else - { - const auto new_texture = this_command_texture_id_ != texture_id; - - if (new_texture) - { - push_texture_id(texture_id); - } - - const auto rounding_left_top = functional::contains(flag, DrawFlag::ROUND_CORNER_LEFT_TOP) ? rounding : 0; - const auto rounding_right_top = functional::contains(flag, DrawFlag::ROUND_CORNER_RIGHT_TOP) ? rounding : 0; - const auto rounding_left_bottom = functional::contains(flag, DrawFlag::ROUND_CORNER_LEFT_BOTTOM) ? rounding : 0; - const auto rounding_right_bottom = functional::contains(flag, DrawFlag::ROUND_CORNER_RIGHT_BOTTOM) ? rounding : 0; - - path_arc_fast({display_rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, ArcQuadrant::Q2_CLOCK_WISH); - path_arc_fast({display_rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, ArcQuadrant::Q1_CLOCK_WISH); - path_arc_fast({display_rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, ArcQuadrant::Q4_CLOCK_WISH); - path_arc_fast({display_rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, ArcQuadrant::Q3_CLOCK_WISH); - - const auto before_vertex_count = vertex_list_.size(); - path_stroke(color); - const auto after_vertex_count = vertex_list_.size(); - - const auto display_size = display_rect.size(); - const auto uv_size = uv_rect.size(); - const auto scale = uv_size / display_size; - - auto it = vertex_list_.begin() + static_cast(before_vertex_count); - const auto end = vertex_list_.begin() + static_cast(after_vertex_count); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(it < end); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(end == vertex_list_.end()); - - // note: linear uv - const auto uv_min = uv_rect.left_top(); - // const auto uv_max = uv_rect.right_bottom(); - while (it != end) - { - const auto v = uv_min + (it->position - display_rect.left_top()) * scale; - - it->uv = - { - // std::ranges::clamp(v.x, uv_min.x, uv_max.x), - v.x, - // std::ranges::clamp(v.y, uv_min.y, uv_max.y) - v.y - }; - it += 1; - } - - if (new_texture) - { - pop_texture_id(); - } - } - } - - constexpr auto path_clear() noexcept -> void - { - path_list_.clear(); - } - - constexpr auto path_reserve(const std::size_t size) noexcept -> void - { - path_list_.reserve(size); - } - - constexpr auto path_reserve_extra(const std::size_t size) noexcept -> void - { - path_reserve(path_list_.size() + size); - } - - constexpr auto path_pin(const point_type& point) noexcept -> void - { - path_list_.push_back(point); - } - - constexpr auto path_stroke(const color_type& color, const DrawFlag flag, const float thickness) noexcept -> void - { - if (functional::contains(draw_list_flag_, DrawListFlag::ANTI_ALIASED_LINE)) - { - draw_polygon_line_aa(color, flag, thickness); - } - else - { - draw_polygon_line(color, flag, thickness); - } - - path_clear(); - } - - constexpr auto path_stroke(const color_type& color) noexcept -> void - { - if (functional::contains(draw_list_flag_, DrawListFlag::ANTI_ALIASED_FILL)) - { - draw_convex_polygon_line_filled_aa(color); - } - else - { - draw_convex_polygon_line_filled(color); - } - - path_clear(); - } - - constexpr auto path_arc_fast(const circle_type& circle, const int from, const int to) noexcept -> void - { - const auto& [center, radius] = circle; - - if (radius < .5f) - { - path_pin(center); - return; - } - - // Calculate arc auto segment step size - auto step = DrawListSharedData::vertex_sample_points_count / shared_data_->get_circle_auto_segment_count(radius); - // Make sure we never do steps larger than one quarter of the circle - step = std::clamp(step, static_cast(1), DrawListSharedData::vertex_sample_points_count / 4); - - const auto sample_range = functional::abs(to - from); - const auto next_step = step; - - auto extra_max_sample = false; - if (step > 1) - { - const auto overstep = sample_range % step; - if (overstep > 0) - { - extra_max_sample = true; - - // When we have overstepped to avoid awkwardly looking one long line and one tiny one at the end, - // distribute first step range evenly between them by reducing first step size. - step -= (step - overstep) / 2; - } - - path_reserve_extra(sample_range / step + 1 + (overstep > 0)); - } - else - { - path_reserve_extra(sample_range + 1); - } - - auto sample_index = from; - if (sample_index < 0 or std::cmp_greater_equal(sample_index, DrawListSharedData::vertex_sample_points_count)) - { - sample_index = sample_index % static_cast(DrawListSharedData::vertex_sample_points_count); - if (sample_index < 0) - { - sample_index += static_cast(DrawListSharedData::vertex_sample_points_count); - } - } - - if (to >= from) - { - for (int i = from; i <= to; i += static_cast(step), sample_index += static_cast(step), step = next_step) - { - // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more - if (std::cmp_greater_equal(sample_index, DrawListSharedData::vertex_sample_points_count)) - { - sample_index -= static_cast(DrawListSharedData::vertex_sample_points_count); - } - - const auto& sample_point = draw_list_detail::vertex_sample_points[sample_index]; - - path_pin({center + sample_point * radius}); - } - } - else - { - for (int i = from; i >= to; i -= static_cast(step), sample_index -= static_cast(step), step = next_step) - { - // a_step is clamped to vertex_sample_points_count, so we have guaranteed that it will not wrap over range twice or more - if (sample_index < 0) - { - sample_index += static_cast(DrawListSharedData::vertex_sample_points_count); - } - - const auto& sample_point = draw_list_detail::vertex_sample_points[sample_index]; - - path_pin({center + sample_point * radius}); - } - } - - if (extra_max_sample) - { - auto normalized_max_sample_index = to % static_cast(DrawListSharedData::vertex_sample_points_count); - if (normalized_max_sample_index < 0) - { - normalized_max_sample_index += DrawListSharedData::vertex_sample_points_count; - } - - const auto& sample_point = draw_list_detail::vertex_sample_points[normalized_max_sample_index]; - - path_pin({center + sample_point * radius}); - } - } - - constexpr auto path_arc_fast(const circle_type& circle, const ArcQuadrant quadrant) noexcept -> void - { - const auto [from, to] = draw_list_detail::range_of_quadrant(quadrant); - - return path_arc_fast(circle, from, to); - } - - constexpr auto path_arc_n(const circle_type& circle, const float from, const float to, const std::uint32_t segments) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(to > from); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(from >= 0); - - const auto& [center, radius] = circle; - - if (radius < .5f) - { - path_pin(center); - return; - } - - path_reserve_extra(segments); - for (std::uint32_t i = 0; i < segments; ++i) - { - const auto a = from + static_cast(i) / static_cast(segments) * (to - from); - path_pin({center + point_type{functional::cos(a), functional::sin(a)} * radius}); - } - } - - constexpr auto path_arc(const circle_type& circle, const float from, const float to) noexcept -> void - { - const auto& [center, radius] = circle; - - if (radius < .5f) - { - path_pin(center); - return; - } - - // Automatic segment count - if (radius <= shared_data_->get_arc_fast_radius_cutoff()) - { - const auto is_reversed = to < from; - - // We are going to use precomputed values for mid-samples. - // Determine first and last sample in lookup table that belong to the arc - const auto sample_from_f = DrawListSharedData::vertex_sample_points_count * from / (std::numbers::pi_v * 2); - const auto sample_to_f = DrawListSharedData::vertex_sample_points_count * to / (std::numbers::pi_v * 2); - - const auto sample_from = is_reversed ? static_cast(functional::floor(sample_from_f)) : static_cast(functional::ceil(sample_from_f)); - const auto sample_to = is_reversed ? static_cast(functional::ceil(sample_to_f)) : static_cast(functional::floor(sample_to_f)); - const auto sample_mid = is_reversed ? static_cast(std::ranges::max(sample_from - sample_to, 0)) : static_cast(std::ranges::max(sample_to - sample_from, 0)); - - const auto segment_from_angle = static_cast(sample_from) * std::numbers::pi_v * 2 / DrawListSharedData::vertex_sample_points_count; - const auto segment_to_angle = static_cast(sample_to) * std::numbers::pi_v * 2 / DrawListSharedData::vertex_sample_points_count; - - const auto emit_start = functional::abs(segment_from_angle - from) >= 1e-5f; - const auto emit_end = functional::abs(to - segment_to_angle) >= 1e-5f; - - if (emit_start) - { - // The quadrant must be the same, otherwise it is not continuous with the path drawn by `path_arc_fast`. - path_pin({center + point_type{functional::cos(from), -functional::sin(from)} * radius}); - } - if (sample_mid > 0) - { - path_arc_fast(circle, sample_from, sample_to); - } - if (emit_end) - { - // The quadrant must be the same, otherwise it is not continuous with the path drawn by `path_arc_fast`. - path_pin({center + point_type{functional::cos(to), -functional::sin(to)} * radius}); - } - } - else - { - const auto arc_length = to - from; - const auto circle_segment_count = shared_data_->get_circle_auto_segment_count(radius); - const auto arc_segment_count = std::ranges::max( - static_cast(functional::ceil(static_cast(circle_segment_count) * arc_length / (std::numbers::pi_v * 2))), - static_cast(std::numbers::pi_v * 2 / arc_length) - ); - path_arc_n(circle, from, to, arc_segment_count); - } - } - - constexpr auto path_arc_elliptical_n(const ellipse_type& ellipse, const float from, const float to, const std::uint32_t segments) noexcept -> void - { - const auto& [center, radius, rotation] = ellipse; - const auto cos_theta = functional::cos(rotation); - const auto sin_theta = functional::sin(rotation); - - path_reserve_extra(segments); - for (std::uint32_t i = 0; i < segments; ++i) - { - const auto a = from + static_cast(i) / static_cast(segments) * (to - from); - const auto offset = point_type{functional::cos(a), functional::sin(a)} * radius; - const auto prime_x = offset.x * cos_theta - offset.y * sin_theta; - const auto prime_y = offset.x * sin_theta + offset.y * cos_theta; - path_pin({center + point_type{prime_x, prime_y}}); - } - } - - constexpr auto path_quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4) noexcept -> void - { - path_pin(p1); - path_pin(p2); - path_pin(p3); - path_pin(p4); - } - - constexpr auto path_rect(const rect_type& rect, float rounding, DrawFlag flag) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(rect.valid() and not rect.empty()); - - if (rounding >= .5f) - { - flag = draw_list_detail::to_fixed_rect_corner_flag(flag); - - const auto v = functional::contains(flag, DrawFlag::ROUND_CORNER_TOP, DrawFlag::ROUND_CORNER_BOTTOM); - const auto h = functional::contains(flag, DrawFlag::ROUND_CORNER_LEFT, DrawFlag::ROUND_CORNER_RIGHT); - - rounding = std::ranges::min(rounding, rect.width() * (v ? .5f : 1.f) - 1.f); - rounding = std::ranges::min(rounding, rect.height() * (h ? .5f : 1.f) - 1.f); - } - - using functional::operators::operator&; - if (rounding < .5f or (DrawFlag::ROUND_CORNER_MASK & flag) == DrawFlag::ROUND_CORNER_NONE) - { - path_quadrilateral(rect.left_top(), rect.right_top(), rect.right_bottom(), rect.left_bottom()); - } - else - { - const auto rounding_left_top = functional::contains(flag, DrawFlag::ROUND_CORNER_LEFT_TOP) ? rounding : 0; - const auto rounding_right_top = functional::contains(flag, DrawFlag::ROUND_CORNER_RIGHT_TOP) ? rounding : 0; - const auto rounding_left_bottom = functional::contains(flag, DrawFlag::ROUND_CORNER_LEFT_BOTTOM) ? rounding : 0; - const auto rounding_right_bottom = functional::contains(flag, DrawFlag::ROUND_CORNER_RIGHT_BOTTOM) ? rounding : 0; - - path_arc_fast({rect.left_top() + point_type{rounding_left_top, rounding_left_top}, rounding_left_top}, ArcQuadrant::Q2_CLOCK_WISH); - path_arc_fast({rect.right_top() + point_type{-rounding_right_top, rounding_right_top}, rounding_right_top}, ArcQuadrant::Q1_CLOCK_WISH); - path_arc_fast({rect.right_bottom() + point_type{-rounding_right_bottom, -rounding_right_bottom}, rounding_right_bottom}, ArcQuadrant::Q4_CLOCK_WISH); - path_arc_fast({rect.left_bottom() + point_type{rounding_left_bottom, -rounding_left_bottom}, rounding_left_bottom}, ArcQuadrant::Q3_CLOCK_WISH); - } - } - - // fixme - constexpr static std::size_t bezier_curve_casteljau_max_level = 10; - - constexpr auto path_bezier_cubic_curve_casteljau( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - const float tessellation_tolerance, - const std::size_t level - ) noexcept -> void - { - const auto dx = p4.x - p1.x; - const auto dy = p4.y - p1.y; - const auto d2 = functional::abs((p2.x - p4.x) * dy - (p2.y - p4.y) * dx); - const auto d3 = functional::abs((p3.x - p4.x) * dy - (p3.y - p4.y) * dx); - - if (functional::pow(d2 + d3, 2) < tessellation_tolerance * (functional::pow(dx, 2) + functional::pow(dy, 2))) - { - path_pin(p4); - } - else if (level < bezier_curve_casteljau_max_level) - { - const auto p_12 = (p1 + p2) * .5f; - const auto p_23 = (p2 + p3) * .5f; - const auto p_34 = (p3 + p4) * .5f; - const auto p_123 = (p_12 + p_23) * .5f; - const auto p_234 = (p_23 + p_34) * .5f; - const auto p_1234 = (p_123 + p_234) * .5f; - - path_bezier_cubic_curve_casteljau(p1, p_12, p_123, p_1234, tessellation_tolerance, level + 1); - path_bezier_cubic_curve_casteljau(p_1234, p_234, p_34, p4, tessellation_tolerance, level + 1); - } - } - - constexpr auto path_bezier_quadratic_curve_casteljau( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const float tessellation_tolerance, - const std::size_t level - ) noexcept -> void - { - const auto dx = p3.x - p1.x; - const auto dy = p3.y - p1.y; - const auto det = (p2.x - p3.x) * dy - (p2.y - p3.y) * dx; - - if (functional::pow(det, 2) * 4.f < tessellation_tolerance * (functional::pow(dx, 2) + functional::pow(dy, 2))) - { - path_pin(p3); - } - else if (level < bezier_curve_casteljau_max_level) - { - const auto p_12 = (p1 + p2) * .5f; - const auto p_23 = (p2 + p3) * .5f; - const auto p_123 = (p_12 + p_23) * .5f; - - path_bezier_quadratic_curve_casteljau(p1, p_12, p_123, tessellation_tolerance, level + 1); - path_bezier_quadratic_curve_casteljau(p_123, p_23, p3, tessellation_tolerance, level + 1); - } - } - - constexpr auto path_bezier_curve( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - const std::uint32_t segments - ) noexcept -> void - { - path_pin(p1); - if (segments == 0) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_->get_curve_tessellation_tolerance() > 0); - - path_reserve_extra(bezier_curve_casteljau_max_level * 2); - // auto-tessellated - path_bezier_cubic_curve_casteljau(p1, p2, p3, p4, shared_data_->get_curve_tessellation_tolerance(), 0); - } - else - { - path_reserve_extra(segments); - const auto step = 1.f / static_cast(segments); - for (std::uint32_t i = 1; i <= segments; ++i) - { - path_pin(draw_list_detail::bezier_cubic_calc(p1, p2, p3, p4, step * static_cast(i))); - } - } - } - - constexpr auto path_bezier_quadratic_curve( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const std::uint32_t segments - ) noexcept -> void - { - path_pin(p1); - if (segments == 0) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_->get_curve_tessellation_tolerance() > 0); - - path_reserve_extra(bezier_curve_casteljau_max_level * 2); - // auto-tessellated - path_bezier_quadratic_curve_casteljau(p1, p2, p3, shared_data_->get_curve_tessellation_tolerance(), 0); - } - else - { - path_reserve_extra(segments); - const auto step = 1.f / static_cast(segments); - for (std::uint32_t i = 1; i <= segments; ++i) - { - path_pin(draw_list_detail::bezier_quadratic_calc(p1, p2, p3, step * static_cast(i))); - } - } - } - - public: - constexpr DrawList() noexcept - : draw_list_flag_{DrawListFlag::NONE}, - this_command_clip_rect_{0, 0, 0, 0}, - this_command_texture_id_{0} {} - - constexpr auto draw_list_flag(const DrawListFlag flag) noexcept -> void - { - draw_list_flag_ = flag; - } - - constexpr auto draw_list_flag(const std::underlying_type_t flag) noexcept -> void - { - draw_list_flag(static_cast(flag)); - } - - auto shared_data(const std::shared_ptr& shared_data) noexcept -> void - { - shared_data_ = shared_data; - } - - constexpr auto reset() noexcept -> void - { - command_list_.resize(0); - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - command_message_.resize(0); - #endif - vertex_list_.resize(0); - index_list_.resize(0); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.resize(0); - debug_list_info_list_.resize(0); - #endif - - // we don't know the size of the clip rect, so we need the user to set it - this_command_clip_rect_ = {}; - // the first texture is always the (default) font texture - this_command_texture_id_ = shared_data_->get_default_font().texture_id; - - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(path_list_.empty()); - - // we always have a command ready in the buffer - command_list_.emplace_back( - command_type - { - .clip_rect = this_command_clip_rect_, - .texture_id = this_command_texture_id_, - .index_offset = index_list_.size(), - // set by subsequent draw_xxx - .element_count = 0 - } - ); - } - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - constexpr auto bind_debug_info() noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(debug_range_info_list_.size() == debug_list_info_list_.size()); - - for (size_type i = 0; i < debug_list_info_list_.size(); ++i) - { - auto& [range_vertices, range_indices] = debug_range_info_list_[i]; - auto& [what, list_vertices, list_indices] = debug_list_info_list_[i]; - list_vertices = {vertex_list_.begin() + range_vertices.first, vertex_list_.begin() + range_vertices.second}; - list_indices = {index_list_.begin() + range_indices.first, index_list_.begin() + range_indices.second}; - } - } - #endif - - [[nodiscard]] constexpr auto command_list() const noexcept -> auto - { - return command_list_ | std::views::all; - } - - [[nodiscard]] constexpr auto vertex_list() const noexcept -> auto - { - return vertex_list_ | std::views::all; - } - - [[nodiscard]] constexpr auto index_list() const noexcept -> auto - { - return index_list_ | std::views::all; - } - - constexpr auto push_clip_rect(const rect_type& rect, const bool intersect_with_current_clip_rect) noexcept -> rect_type& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not command_list_.empty()); - - const auto& [current_clip_rect, current_texture, current_index_offset, current_element_count] = command_list_.back(); - - this_command_clip_rect_ = intersect_with_current_clip_rect ? rect.combine_min(current_clip_rect) : rect; - - on_element_changed(); - return command_list_.back().clip_rect; - } - - constexpr auto push_clip_rect(const point_type& left_top, const point_type& right_bottom, const bool intersect_with_current_clip_rect) noexcept -> rect_type& - { - return push_clip_rect({left_top, right_bottom}, intersect_with_current_clip_rect); - } - - constexpr auto pop_clip_rect() noexcept -> void - { - // todo - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list_.size() > 1); - this_command_clip_rect_ = command_list_[command_list_.size() - 2].clip_rect; - - on_element_changed(); - } - - constexpr auto push_texture_id(const texture_id_type texture) noexcept -> void - { - this_command_texture_id_ = texture; - - on_element_changed(); - } - - constexpr auto pop_texture_id() noexcept -> void - { - // todo - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(command_list_.size() > 1); - this_command_texture_id_ = command_list_[command_list_.size() - 2].texture_id; - - on_element_changed(); - } - - constexpr auto line(const point_type& from, const point_type& to, const color_type& color, const float thickness = 1.f) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0) - { - return; - } - - path_pin(from + point_type{.5f, .5f}); - path_pin(to + point_type{.5f, .5f}); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - path_stroke(color, DrawFlag::NONE, thickness); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[LINE] [{} => {}]{}({:.3f})", from, to, color, thickness), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - - constexpr auto triangle(const point_type& a, const point_type& b, const point_type& c, const color_type& color, const float thickness = 1.f) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0) - { - return; - } - - path_pin(a); - path_pin(b); - path_pin(c); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - path_stroke(color, DrawFlag::CLOSED, thickness); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[TRIANGLE] [{} △ {} △ {}]{}({:.3f})", a, b, c, color, thickness), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - - constexpr auto triangle_filled(const point_type& a, const point_type& b, const point_type& c, const color_type& color) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0) - { - return; - } - - path_pin(a); - path_pin(b); - path_pin(c); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - path_stroke(color); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[TRIANGLE FILLED] [{} ▲ {} ▲ {}]{}", a, b, c, color), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - - constexpr auto rect(const rect_type& rect, const color_type& color, const float rounding = .0f, const DrawFlag flag = DrawFlag::NONE, const float thickness = 1.f) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0) - { - return; - } - - path_rect(rect_type{rect.left_top() + point_type{.5f, .5f}, rect.right_bottom() - point_type{.5f, .5f}}, rounding, flag); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - path_stroke(color, DrawFlag::CLOSED, thickness); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[RECT] □ {}{}({:.3f})", rect, color, thickness), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - - constexpr auto rect( - const point_type& left_top, - const point_type& right_bottom, - const color_type& color, - const float rounding = .0f, - const DrawFlag flag = DrawFlag::NONE, - const float thickness = 1.f - ) noexcept -> void - { - return rect(rect_type{left_top, right_bottom}, color, rounding, flag, thickness); - } - - constexpr auto rect_filled(const rect_type& rect, const color_type& color, const float rounding = .0f, const DrawFlag flag = DrawFlag::NONE) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0) - { - return; - } - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - using functional::operators::operator&; - if (rounding < .5f or (DrawFlag::ROUND_CORNER_MASK & flag) == DrawFlag::ROUND_CORNER_NONE) - { - draw_rect_filled(rect, color, color, color, color); - } - else - { - path_rect(rect, rounding, flag); - path_stroke(color); - } - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[RECT FILLED] ■ {}{}", rect, color), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - - constexpr auto rect_filled( - const point_type& left_top, - const point_type& right_bottom, - const color_type& color, - const float rounding = .0f, - const DrawFlag flag = DrawFlag::NONE - ) noexcept -> void - { - return rect_filled(rect_type{left_top, right_bottom}, color, rounding, flag); - } - - constexpr auto rect_filled( - const rect_type& rect, - const color_type& color_left_top, - const color_type& color_right_top, - const color_type& color_left_bottom, - const color_type& color_right_bottom - ) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color_left_top.alpha == 0 or color_right_top.alpha == 0 or color_left_bottom.alpha == 0 or color_right_bottom.alpha == 0) - { - return; - } - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - draw_rect_filled(rect, color_left_top, color_right_top, color_left_bottom, color_right_bottom); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[RECT FILLED] ■ {}({}/{}/{}/{})", rect, color_left_top, color_right_top, color_left_bottom, color_right_bottom), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - - constexpr auto rect_filled( - const point_type& left_top, - const point_type& right_bottom, - const color_type& color_left_top, - const color_type& color_right_top, - const color_type& color_left_bottom, - const color_type& color_right_bottom - ) noexcept -> void - { - return rect_filled({left_top, right_bottom}, color_left_top, color_right_top, color_left_bottom, color_right_bottom); - } - - constexpr auto quadrilateral(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const color_type& color, const float thickness = 1.f) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0) - { - return; - } - - path_quadrilateral(p1, p2, p3, p4); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - path_stroke(color, DrawFlag::CLOSED, thickness); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[QUADRILATERAL] ▱ [{}-{}-{}-{}]{}({:.3f})", p1, p2, p3, p4, color, thickness), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - - constexpr auto quadrilateral_filled(const point_type& p1, const point_type& p2, const point_type& p3, const point_type& p4, const color_type& color) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0) - { - return; - } - - path_quadrilateral(p1, p2, p3, p4); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - path_stroke(color); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[QUADRILATERAL FILLED] ▰ [{}-{}-{}-{}]{}", p1, p2, p3, p4, color), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - - constexpr auto circle_n(const circle_type& circle, const color_type& color, const std::uint32_t segments, const float thickness = 1.f) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0 or circle.radius < .5f or segments < 3) - { - return; - } - - path_arc_n(circle, 0, std::numbers::pi_v * 2, segments); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - path_stroke(color, DrawFlag::CLOSED, thickness); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[CIRCLE] ○ {}{}({:.3f})", circle, color, thickness), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - - constexpr auto circle_n(const point_type& center, const float radius, const color_type& color, const std::uint32_t segments, const float thickness = 1.f) noexcept -> void - { - return circle_n({center, radius}, color, segments, thickness); - } - - constexpr auto ellipse_n(const ellipse_type& ellipse, const color_type& color, const std::uint32_t segments, const float thickness = 1.f) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f or segments < 3) - { - return; - } - - path_arc_elliptical_n(ellipse, 0, std::numbers::pi_v * 2, segments); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - path_stroke(color, DrawFlag::CLOSED, thickness); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[ELLIPSE] ○ {}{}({:.3f})", ellipse, color, thickness), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - - constexpr auto ellipse_n( - const point_type& center, - const extent_type& radius, - const float rotation, - const color_type& color, - const std::uint32_t segments, - const float thickness = 1.f - ) noexcept -> void - { - return ellipse_n({center, radius, rotation}, color, segments, thickness); - } - - constexpr auto circle_n_filled(const circle_type& circle, const color_type& color, const std::uint32_t segments) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0 or circle.radius < .5f or segments < 3) - { - return; - } - - path_arc_n(circle, 0, std::numbers::pi_v * 2, segments); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - path_stroke(color); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[CIRCLE FILLED] ● {}{}", circle, color), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - - constexpr auto circle_n_filled(const point_type& center, const float radius, const color_type& color, const std::uint32_t segments) noexcept -> void - { - return circle_n_filled({center, radius}, color, segments); - } - - constexpr auto ellipse_n_filled(const ellipse_type& ellipse, const color_type& color, const std::uint32_t segments) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f or segments < 3) - { - return; - } - - path_arc_elliptical_n(ellipse, 0, std::numbers::pi_v * 2, segments); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - path_stroke(color); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[ELLIPSE FILLED] ● {}{}", ellipse, color), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - - constexpr auto ellipse_n_filled( - const point_type& center, - const extent_type& radius, - const float rotation, - const color_type& color, - const std::uint32_t segments - ) noexcept -> void - { - return ellipse_n_filled({center, radius, rotation}, color, segments); - } - - constexpr auto circle(const circle_type& circle, const color_type& color, const std::uint32_t segments = 0, const float thickness = 1.f) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0 or circle.radius < .5f) - { - return; - } - - if (segments == 0) - { - path_arc_fast(circle, 0, DrawListSharedData::vertex_sample_points_count - 1); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - path_stroke(color, DrawFlag::CLOSED, thickness); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[CIRCLE] ○ {}{}({:.3f})", circle, color, thickness), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - else - { - const auto clamped_segments = std::ranges::clamp(segments, draw_list_detail::circle_segments_min, draw_list_detail::circle_segments_max); - - circle_n(circle, color, clamped_segments, thickness); - } - } - - constexpr auto circle(const point_type& center, const float radius, const color_type& color, const std::uint32_t segments = 0, const float thickness = 1.f) noexcept -> void - { - return circle({center, radius}, color, segments, thickness); - } - - constexpr auto circle_filled(const circle_type& circle, const color_type& color, const std::uint32_t segments = 0) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0 or circle.radius < .5f) - { - return; - } - - if (segments == 0) - { - path_arc_fast(circle, 0, DrawListSharedData::vertex_sample_points_count - 1); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - path_stroke(color); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[CIRCLE FILLED] ● {}{}", circle, color), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - else - { - const auto clamped_segments = std::ranges::clamp(segments, draw_list_detail::circle_segments_min, draw_list_detail::circle_segments_max); - - circle_n_filled(circle, color, clamped_segments); - } - } - - constexpr auto circle_filled(const point_type& center, const float radius, const color_type& color, const std::uint32_t segments = 0) noexcept -> void - { - return circle_filled({center, radius}, color, segments); - } - - constexpr auto ellipse(const ellipse_type& ellipse, const color_type& color, std::uint32_t segments = 0, const float thickness = 1.f) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) - { - return; - } - - if (segments == 0) - { - // fixme: maybe there's a better computation to do here - segments = shared_data_->get_circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); - } - - ellipse_n(ellipse, color, segments, thickness); - } - - constexpr auto ellipse( - const point_type& center, - const extent_type& radius, - const float rotation, - const color_type& color, - const std::uint32_t segments = 0, - const float thickness = 1.f - ) noexcept -> void - { - return ellipse({center, radius, rotation}, color, segments, thickness); - } - - constexpr auto ellipse_filled(const ellipse_type& ellipse, const color_type& color, std::uint32_t segments = 0) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0 or ellipse.radius.width < .5f or ellipse.radius.height < .5f) - { - return; - } - - if (segments == 0) - { - // fixme: maybe there's a better computation to do here - segments = shared_data_->get_circle_auto_segment_count(std::ranges::max(ellipse.radius.width, ellipse.radius.height)); - } - - ellipse_n_filled(ellipse, color, segments); - } - - constexpr auto ellipse_filled( - const point_type& center, - const extent_type& radius, - const float rotation, - const color_type& color, - const std::uint32_t segments = 0 - ) noexcept -> void - { - return ellipse_filled({center, radius, rotation}, color, segments); - } - - constexpr auto bezier_cubic( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const point_type& p4, - const color_type& color, - const std::uint32_t segments = 0, - const float thickness = 1.f - ) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0) - { - return; - } - - path_bezier_curve(p1, p2, p3, p4, segments); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - path_stroke(color, DrawFlag::NONE, thickness); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[BEZIER CUBIC] ∫ [{}-{}-{}-{}]{}({:.3f})", p1, p2, p3, p4, color, thickness), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - - constexpr auto bezier_quadratic( - const point_type& p1, - const point_type& p2, - const point_type& p3, - const color_type& color, - const std::uint32_t segments = 0, - const float thickness = 1.f - ) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0) - { - return; - } - - path_bezier_quadratic_curve(p1, p2, p3, segments); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - path_stroke(color, DrawFlag::NONE, thickness); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[BEZIER QUADRATIC] ৲ [{}-{}-{}]{}({:.3f})", p1, p2, p3, color, thickness), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - - constexpr auto text( - const font_type& font, - const float font_size, - const point_type& p, - const color_type& color, - const std::string_view text, - const float wrap_width = .0f - ) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0) { return; } - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - draw_text(font, font_size, p, color, text, wrap_width); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[TEXT] [{}({:.3f}): {}]({})", p, font_size, text, color), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - - constexpr auto text( - const float font_size, - const point_type& p, - const color_type& color, - const std::string_view text, - const float wrap_width = .0f - ) noexcept -> void - { - this->text(shared_data_->get_default_font(), font_size, p, color, text, wrap_width); - } - - //p1 ________ p2 - // | | - // | | - //p4 |__ ____| p3 - constexpr auto image( - const texture_id_type texture_id, - const point_type& display_p1, - const point_type& display_p2, - const point_type& display_p3, - const point_type& display_p4, - const uv_type& uv_p1 = {0, 0}, - const uv_type& uv_p2 = {1, 0}, - const uv_type& uv_p3 = {1, 1}, - const uv_type& uv_p4 = {0, 1}, - const color_type& color = primitive::colors::white - ) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0) { return; } - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - draw_image(texture_id, display_p1, display_p2, display_p3, display_p4, uv_p1, uv_p2, uv_p3, uv_p4, color); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[IMAGE] [{}: {}-{}-{}-{}/{}-{}-{}-{}]{}", texture_id, display_p1, display_p2, display_p3, display_p4, uv_p1, uv_p2, uv_p3, uv_p4, color), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - - constexpr auto image( - const texture_id_type texture_id, - const rect_type& display_rect, - const rect_type& uv_rect = {0, 0, 1, 1}, - const color_type& color = primitive::colors::white - ) noexcept -> void - { - image( - texture_id, - display_rect.left_top(), - display_rect.right_top(), - display_rect.right_bottom(), - display_rect.left_bottom(), - uv_rect.left_top(), - uv_rect.right_top(), - uv_rect.right_bottom(), - uv_rect.left_bottom(), - color - ); - } - - constexpr auto image( - const texture_id_type texture_id, - const point_type& display_left_top, - const point_type& display_right_bottom, - const uv_type& uv_left_top = {0, 0}, - const uv_type& uv_right_bottom = {1, 1}, - const color_type& color = primitive::colors::white - ) noexcept -> void - { - image(texture_id, {display_left_top, display_right_bottom}, {uv_left_top, uv_right_bottom}, color); - } - - constexpr auto image_rounded( - const texture_id_type texture_id, - const rect_type& display_rect, - const float rounding = .0f, - const DrawFlag flag = DrawFlag::NONE, - const rect_type& uv_rect = {0, 0, 1, 1}, - const color_type& color = primitive::colors::white - ) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(shared_data_ != nullptr); - - if (color.alpha == 0) - { - return; - } - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - const auto current_vertex_size = vertex_list_.size(); - const auto current_index_size = index_list_.size(); - #endif - - draw_image_rounded(texture_id, display_rect, uv_rect, color, rounding, flag); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - debug_range_info_list_.emplace_back( - debug_vertex_range_type{current_vertex_size, vertex_list_.size()}, - debug_index_range_type{current_index_size, index_list_.size()} - ); - debug_list_info_list_.emplace_back( - std::format("[IMAGE] [{}: {}/{}]{}", texture_id, display_rect, uv_rect, color), - // the data stored now is unreliable - debug_vertex_list_type{vertex_list_.begin() + current_vertex_size, vertex_list_.end()}, - // the data stored now is unreliable - debug_index_list_type{index_list_.begin() + current_index_size, index_list_.end()} - ); - #endif - } - - constexpr auto image_rounded( - const texture_id_type texture_id, - const point_type& display_left_top, - const point_type& display_right_bottom, - const float rounding = .0f, - const DrawFlag flag = DrawFlag::NONE, - const uv_type& uv_left_top = {0, 0}, - const uv_type& uv_right_bottom = {1, 1}, - const color_type& color = primitive::colors::white - ) noexcept -> void - { - image_rounded(texture_id, {display_left_top, display_right_bottom}, rounding, flag, {uv_left_top, uv_right_bottom}, color); - } - }; - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -} diff --git a/src/gui/font.impl.ixx b/src/gui/font.impl.ixx deleted file mode 100644 index 21acb9da..00000000 --- a/src/gui/font.impl.ixx +++ /dev/null @@ -1,356 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -#include -#include FT_FREETYPE_H // -// #include FT_MODULE_H // -// #include FT_GLYPH_H // -// #include FT_SYNTHESIS_H // - -#define STB_RECT_PACK_IMPLEMENTATION -#include - -export module gal.prometheus.gui:font.impl; - -import std; - -import :font; - -#else -#include -#include -#include - -#include -#include FT_FREETYPE_H // -// #include FT_MODULE_H // -// #include FT_GLYPH_H // -// #include FT_SYNTHESIS_H // - -#define STB_RECT_PACK_IMPLEMENTATION -#include - -#include -#include - -#endif - -namespace -{ - using namespace gal::prometheus::gui; - - // ReSharper disable once CppInconsistentNaming - constexpr glyph_range_value_type simplified_chinese_common_accumulative_offsets_from_0x4e00[] - { - 0, 1, 2, 4, 1, 1, 1, 1, 2, 1, 3, 2, 1, 2, 2, 1, 1, 1, 1, 1, 5, 2, 1, 2, 3, 3, 3, 2, 2, 4, 1, 1, 1, 2, 1, 5, 2, 3, 1, 2, 1, 2, - 1, 1, 2, 1, 1, 2, 2, 1, 4, 1, 1, 1, 1, 5, 10, 1, 2, 19, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 5, 1, 6, 3, 2, 1, 2, 2, 1, 1, 1, 4, 8, 5, - 1, 1, 4, 1, 1, 3, 1, 2, 1, 5, 1, 2, 1, 1, 1, 10, 1, 1, 5, 2, 4, 6, 1, 4, 2, 2, 2, 12, 2, 1, 1, 6, 1, 1, 1, 4, 1, 1, 4, 6, 5, 1, - 4, 2, 2, 4, 10, 7, 1, 1, 4, 2, 4, 2, 1, 4, 3, 6, 10, 12, 5, 7, 2, 14, 2, 9, 1, 1, 6, 7, 10, 4, 7, 13, 1, 5, 4, 8, 4, 1, 1, 2, 28, 5, - 6, 1, 1, 5, 2, 5, 20, 2, 2, 9, 8, 11, 2, 9, 17, 1, 8, 6, 8, 27, 4, 6, 9, 20, 11, 27, 6, 68, 2, 2, 1, 1, 1, 2, 1, 2, 2, 7, 6, 11, 3, 3, - 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, 3, 1, 1, 8, 3, 4, 1, 5, 7, 2, 1, 4, 4, 8, 4, 2, 1, 2, 1, 1, 4, 5, 6, 3, 6, 2, 12, 3, 1, 3, 9, 2, - 4, 3, 4, 1, 5, 3, 3, 1, 3, 7, 1, 5, 1, 1, 1, 1, 2, 3, 4, 5, 2, 3, 2, 6, 1, 1, 2, 1, 7, 1, 7, 3, 4, 5, 15, 2, 2, 1, 5, 3, 22, 19, - 2, 1, 1, 1, 1, 2, 5, 1, 1, 1, 6, 1, 1, 12, 8, 2, 9, 18, 22, 4, 1, 1, 5, 1, 16, 1, 2, 7, 10, 15, 1, 1, 6, 2, 4, 1, 2, 4, 1, 6, 1, 1, - 3, 2, 4, 1, 6, 4, 5, 1, 2, 1, 1, 2, 1, 10, 3, 1, 3, 2, 1, 9, 3, 2, 5, 7, 2, 19, 4, 3, 6, 1, 1, 1, 1, 1, 4, 3, 2, 1, 1, 1, 2, 5, - 3, 1, 1, 1, 2, 2, 1, 1, 2, 1, 1, 2, 1, 3, 1, 1, 1, 3, 7, 1, 4, 1, 1, 2, 1, 1, 2, 1, 2, 4, 4, 3, 8, 1, 1, 1, 2, 1, 3, 5, 1, 3, - 1, 3, 4, 6, 2, 2, 14, 4, 6, 6, 11, 9, 1, 15, 3, 1, 28, 5, 2, 5, 5, 3, 1, 3, 4, 5, 4, 6, 14, 3, 2, 3, 5, 21, 2, 7, 20, 10, 1, 2, 19, 2, - 4, 28, 28, 2, 3, 2, 1, 14, 4, 1, 26, 28, 42, 12, 40, 3, 52, 79, 5, 14, 17, 3, 2, 2, 11, 3, 4, 6, 3, 1, 8, 2, 23, 4, 5, 8, 10, 4, 2, 7, 3, 5, - 1, 1, 6, 3, 1, 2, 2, 2, 5, 28, 1, 1, 7, 7, 20, 5, 3, 29, 3, 17, 26, 1, 8, 4, 27, 3, 6, 11, 23, 5, 3, 4, 6, 13, 24, 16, 6, 5, 10, 25, 35, 7, - 3, 2, 3, 3, 14, 3, 6, 2, 6, 1, 4, 2, 3, 8, 2, 1, 1, 3, 3, 3, 4, 1, 1, 13, 2, 2, 4, 5, 2, 1, 14, 14, 1, 2, 2, 1, 4, 5, 2, 3, 1, 14, - 3, 12, 3, 17, 2, 16, 5, 1, 2, 1, 8, 9, 3, 19, 4, 2, 2, 4, 17, 25, 21, 20, 28, 75, 1, 10, 29, 103, 4, 1, 2, 1, 1, 4, 2, 4, 1, 2, 3, 24, 2, 2, - 2, 1, 1, 2, 1, 3, 8, 1, 1, 1, 2, 1, 1, 3, 1, 1, 1, 6, 1, 5, 3, 1, 1, 1, 3, 4, 1, 1, 5, 2, 1, 5, 6, 13, 9, 16, 1, 1, 1, 1, 3, 2, - 3, 2, 4, 5, 2, 5, 2, 2, 3, 7, 13, 7, 2, 2, 1, 1, 1, 1, 2, 3, 3, 2, 1, 6, 4, 9, 2, 1, 14, 2, 14, 2, 1, 18, 3, 4, 14, 4, 11, 41, 15, 23, - 15, 23, 176, 1, 3, 4, 1, 1, 1, 1, 5, 3, 1, 2, 3, 7, 3, 1, 1, 2, 1, 2, 4, 4, 6, 2, 4, 1, 9, 7, 1, 10, 5, 8, 16, 29, 1, 1, 2, 2, 3, 1, - 3, 5, 2, 4, 5, 4, 1, 1, 2, 2, 3, 3, 7, 1, 6, 10, 1, 17, 1, 44, 4, 6, 2, 1, 1, 6, 5, 4, 2, 10, 1, 6, 9, 2, 8, 1, 24, 1, 2, 13, 7, 8, - 8, 2, 1, 4, 1, 3, 1, 3, 3, 5, 2, 5, 10, 9, 4, 9, 12, 2, 1, 6, 1, 10, 1, 1, 7, 7, 4, 10, 8, 3, 1, 13, 4, 3, 1, 6, 1, 3, 5, 2, 1, 2, - 17, 16, 5, 2, 16, 6, 1, 4, 2, 1, 3, 3, 6, 8, 5, 11, 11, 1, 3, 3, 2, 4, 6, 10, 9, 5, 7, 4, 7, 4, 7, 1, 1, 4, 2, 1, 3, 6, 8, 7, 1, 6, - 11, 5, 5, 3, 24, 9, 4, 2, 7, 13, 5, 1, 8, 82, 16, 61, 1, 1, 1, 4, 2, 2, 16, 10, 3, 8, 1, 1, 6, 4, 2, 1, 3, 1, 1, 1, 4, 3, 8, 4, 2, 2, - 1, 1, 1, 1, 1, 6, 3, 5, 1, 1, 4, 6, 9, 2, 1, 1, 1, 2, 1, 7, 2, 1, 6, 1, 5, 4, 4, 3, 1, 8, 1, 3, 3, 1, 3, 2, 2, 2, 2, 3, 1, 6, - 1, 2, 1, 2, 1, 3, 7, 1, 8, 2, 1, 2, 1, 5, 2, 5, 3, 5, 10, 1, 2, 1, 1, 3, 2, 5, 11, 3, 9, 3, 5, 1, 1, 5, 9, 1, 2, 1, 5, 7, 9, 9, - 8, 1, 3, 3, 3, 6, 8, 2, 3, 2, 1, 1, 32, 6, 1, 2, 15, 9, 3, 7, 13, 1, 3, 10, 13, 2, 14, 1, 13, 10, 2, 1, 3, 10, 4, 15, 2, 15, 15, 10, 1, 3, - 9, 6, 9, 32, 25, 26, 47, 7, 3, 2, 3, 1, 6, 3, 4, 3, 2, 8, 5, 4, 1, 9, 4, 2, 2, 19, 10, 6, 2, 3, 8, 1, 2, 2, 4, 2, 1, 9, 4, 4, 4, 6, - 4, 8, 9, 2, 3, 1, 1, 1, 1, 3, 5, 5, 1, 3, 8, 4, 6, 2, 1, 4, 12, 1, 5, 3, 7, 13, 2, 5, 8, 1, 6, 1, 2, 5, 14, 6, 1, 5, 2, 4, 8, 15, - 5, 1, 23, 6, 62, 2, 10, 1, 1, 8, 1, 2, 2, 10, 4, 2, 2, 9, 2, 1, 1, 3, 2, 3, 1, 5, 3, 3, 2, 1, 3, 8, 1, 1, 1, 11, 3, 1, 1, 4, 3, 7, - 1, 14, 1, 2, 3, 12, 5, 2, 5, 1, 6, 7, 5, 7, 14, 11, 1, 3, 1, 8, 9, 12, 2, 1, 11, 8, 4, 4, 2, 6, 10, 9, 13, 1, 1, 3, 1, 5, 1, 3, 2, 4, - 4, 1, 18, 2, 3, 14, 11, 4, 29, 4, 2, 7, 1, 3, 13, 9, 2, 2, 5, 3, 5, 20, 7, 16, 8, 5, 72, 34, 6, 4, 22, 12, 12, 28, 45, 36, 9, 7, 39, 9, 191, 1, - 1, 1, 4, 11, 8, 4, 9, 2, 3, 22, 1, 1, 1, 1, 4, 17, 1, 7, 7, 1, 11, 31, 10, 2, 4, 8, 2, 3, 2, 1, 4, 2, 16, 4, 32, 2, 3, 19, 13, 4, 9, 1, - 5, 2, 14, 8, 1, 1, 3, 6, 19, 6, 5, 1, 16, 6, 2, 10, 8, 5, 1, 2, 3, 1, 5, 5, 1, 11, 6, 6, 1, 3, 3, 2, 6, 3, 8, 1, 1, 4, 10, 7, 5, 7, - 7, 5, 8, 9, 2, 1, 3, 4, 1, 1, 3, 1, 3, 3, 2, 6, 16, 1, 4, 6, 3, 1, 10, 6, 1, 3, 15, 2, 9, 2, 10, 25, 13, 9, 16, 6, 2, 2, 10, 11, 4, 3, - 9, 1, 2, 6, 6, 5, 4, 30, 40, 1, 10, 7, 12, 14, 33, 6, 3, 6, 7, 3, 1, 3, 1, 11, 14, 4, 9, 5, 12, 11, 49, 18, 51, 31, 140, 31, 2, 2, 1, 5, 1, 8, - 1, 10, 1, 4, 4, 3, 24, 1, 10, 1, 3, 6, 6, 16, 3, 4, 5, 2, 1, 4, 2, 57, 10, 6, 22, 2, 22, 3, 7, 22, 6, 10, 11, 36, 18, 16, 33, 36, 2, 5, 5, 1, - 1, 1, 4, 10, 1, 4, 13, 2, 7, 5, 2, 9, 3, 4, 1, 7, 43, 3, 7, 3, 9, 14, 7, 9, 1, 11, 1, 1, 3, 7, 4, 18, 13, 1, 14, 1, 3, 6, 10, 73, 2, 2, - 30, 6, 1, 11, 18, 19, 13, 22, 3, 46, 42, 37, 89, 7, 3, 16, 34, 2, 2, 3, 9, 1, 7, 1, 1, 1, 2, 2, 4, 10, 7, 3, 10, 3, 9, 5, 28, 9, 2, 6, 13, 7, - 3, 1, 3, 10, 2, 7, 2, 11, 3, 6, 21, 54, 85, 2, 1, 4, 2, 2, 1, 39, 3, 21, 2, 2, 5, 1, 1, 1, 4, 1, 1, 3, 4, 15, 1, 3, 2, 4, 4, 2, 3, 8, - 2, 20, 1, 8, 7, 13, 4, 1, 26, 6, 2, 9, 34, 4, 21, 52, 10, 4, 4, 1, 5, 12, 2, 11, 1, 7, 2, 30, 12, 44, 2, 30, 1, 1, 3, 6, 16, 9, 17, 39, 82, 2, - 2, 24, 7, 1, 7, 3, 16, 9, 14, 44, 2, 1, 2, 1, 2, 3, 5, 2, 4, 1, 6, 7, 5, 3, 2, 6, 1, 11, 5, 11, 2, 1, 18, 19, 8, 1, 3, 24, 29, 2, 1, 3, - 5, 2, 2, 1, 13, 6, 5, 1, 46, 11, 3, 5, 1, 1, 5, 8, 2, 10, 6, 12, 6, 3, 7, 11, 2, 4, 16, 13, 2, 5, 1, 1, 2, 2, 5, 2, 28, 5, 2, 23, 10, 8, - 4, 4, 22, 39, 95, 38, 8, 14, 9, 5, 1, 13, 5, 4, 3, 13, 12, 11, 1, 9, 1, 27, 37, 2, 5, 4, 4, 63, 211, 95, 2, 2, 2, 1, 3, 5, 2, 1, 1, 2, 2, 1, - 1, 1, 3, 2, 4, 1, 2, 1, 1, 5, 2, 2, 1, 1, 2, 3, 1, 3, 1, 1, 1, 3, 1, 4, 2, 1, 3, 6, 1, 1, 3, 7, 15, 5, 3, 2, 5, 3, 9, 11, 4, 2, - 22, 1, 6, 3, 8, 7, 1, 4, 28, 4, 16, 3, 3, 25, 4, 4, 27, 27, 1, 4, 1, 2, 2, 7, 1, 3, 5, 2, 28, 8, 2, 14, 1, 8, 6, 16, 25, 3, 3, 3, 14, 3, - 3, 1, 1, 2, 1, 4, 6, 3, 8, 4, 1, 1, 1, 2, 3, 6, 10, 6, 2, 3, 18, 3, 2, 5, 5, 4, 3, 1, 5, 2, 5, 4, 23, 7, 6, 12, 6, 4, 17, 11, 9, 5, - 1, 1, 10, 5, 12, 1, 1, 11, 26, 33, 7, 3, 6, 1, 17, 7, 1, 5, 12, 1, 11, 2, 4, 1, 8, 14, 17, 23, 1, 2, 1, 7, 8, 16, 11, 9, 6, 5, 2, 6, 4, 16, - 2, 8, 14, 1, 11, 8, 9, 1, 1, 1, 9, 25, 4, 11, 19, 7, 2, 15, 2, 12, 8, 52, 7, 5, 19, 2, 16, 4, 36, 8, 1, 16, 8, 24, 26, 4, 6, 2, 9, 5, 4, 36, - 3, 28, 12, 25, 15, 37, 27, 17, 12, 59, 38, 5, 32, 127, 1, 2, 9, 17, 14, 4, 1, 2, 1, 1, 8, 11, 50, 4, 14, 2, 19, 16, 4, 17, 5, 4, 5, 26, 12, 45, 2, 23, - 45, 104, 30, 12, 8, 3, 10, 2, 2, 3, 3, 1, 4, 20, 7, 2, 9, 6, 15, 2, 20, 1, 3, 16, 4, 11, 15, 6, 134, 2, 5, 59, 1, 2, 2, 2, 1, 9, 17, 3, 26, 137, - 10, 211, 59, 1, 2, 4, 1, 4, 1, 1, 1, 2, 6, 2, 3, 1, 1, 2, 3, 2, 3, 1, 3, 4, 4, 2, 3, 3, 1, 4, 3, 1, 7, 2, 2, 3, 1, 2, 1, 3, 3, 3, - 2, 2, 3, 2, 1, 3, 14, 6, 1, 3, 2, 9, 6, 15, 27, 9, 34, 145, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 2, 2, 3, 1, 2, 1, 1, 1, 2, 3, 5, - 8, 3, 5, 2, 4, 1, 3, 2, 2, 2, 12, 4, 1, 1, 1, 10, 4, 5, 1, 20, 4, 16, 1, 15, 9, 5, 12, 2, 9, 2, 5, 4, 2, 26, 19, 7, 1, 26, 4, 30, 12, 15, - 42, 1, 6, 8, 172, 1, 1, 4, 2, 1, 1, 11, 2, 2, 4, 2, 1, 2, 1, 10, 8, 1, 2, 1, 4, 5, 1, 2, 5, 1, 8, 4, 1, 3, 4, 2, 1, 6, 2, 1, 3, 4, - 1, 2, 1, 1, 1, 1, 12, 5, 7, 2, 4, 3, 1, 1, 1, 3, 3, 6, 1, 2, 2, 3, 3, 3, 2, 1, 2, 12, 14, 11, 6, 6, 4, 12, 2, 8, 1, 7, 10, 1, 35, 7, - 4, 13, 15, 4, 3, 23, 21, 28, 52, 5, 26, 5, 6, 1, 7, 10, 2, 7, 53, 3, 2, 1, 1, 1, 2, 163, 532, 1, 10, 11, 1, 3, 3, 4, 8, 2, 8, 6, 2, 2, 23, 22, - 4, 2, 2, 4, 2, 1, 3, 1, 3, 3, 5, 9, 8, 2, 1, 2, 8, 1, 10, 2, 12, 21, 20, 15, 105, 2, 3, 1, 1, 3, 2, 3, 1, 1, 2, 5, 1, 4, 15, 11, 19, 1, - 1, 1, 1, 5, 4, 5, 1, 1, 2, 5, 3, 5, 12, 1, 2, 5, 1, 11, 1, 1, 15, 9, 1, 4, 5, 3, 26, 8, 2, 1, 3, 1, 1, 15, 19, 2, 12, 1, 2, 5, 2, 7, - 2, 19, 2, 20, 6, 26, 7, 5, 2, 2, 7, 34, 21, 13, 70, 2, 128, 1, 1, 2, 1, 1, 2, 1, 1, 3, 2, 2, 2, 15, 1, 4, 1, 3, 4, 42, 10, 6, 1, 49, 85, 8, - 1, 2, 1, 1, 4, 4, 2, 3, 6, 1, 5, 7, 4, 3, 211, 4, 1, 2, 1, 2, 5, 1, 2, 4, 2, 2, 6, 5, 6, 10, 3, 4, 48, 100, 6, 2, 16, 296, 5, 27, 387, 2, - 2, 3, 7, 16, 8, 5, 38, 15, 39, 21, 9, 10, 3, 7, 59, 13, 27, 21, 47, 5, 21, 6 - }; -} - -namespace gal::prometheus::gui -{ - [[nodiscard]] auto glyph_range_latin() noexcept -> glyph_ranges_view_type - { - constexpr static std::array range{{ - {0x0020, 0x00ff}, // Basic Latin + Latin Supplement - }}; - - return range; - } - - [[nodiscard]] auto glyph_range_greek() noexcept -> glyph_ranges_view_type - { - constexpr static std::array range{{ - {0x0020, 0x00ff}, // Basic Latin + Latin Supplement - {0x0370, 0x3ff}, // Greek and Coptic - }}; - - return range; - } - - [[nodiscard]] auto glyph_range_simplified_chinese_common() noexcept -> glyph_ranges_view_type - { - const auto unpack = []() constexpr noexcept -> glyph_range_type - { - const auto codepoint = - 0x4e00 + - // // fixme: fatal error C1128: number of sections exceeded object file format limit : compile with /bigobj - // std::ranges::fold_left( - // std::ranges::subrange{ - // std::ranges::begin(simplified_chinese_common_accumulative_offsets_from_0x4e00), - // std::ranges::begin(simplified_chinese_common_accumulative_offsets_from_0x4e00) + 1 + I - // }, - // glyph_range_value_type{0}, - // [](const glyph_range_value_type total, const glyph_range_value_type current) noexcept -> glyph_range_value_type - // { - // return total + current; - // } - // ); - std::accumulate( - std::ranges::begin(simplified_chinese_common_accumulative_offsets_from_0x4e00), - std::ranges::begin(simplified_chinese_common_accumulative_offsets_from_0x4e00) + 1 + I, - glyph_range_value_type{0} - ); - return {codepoint, codepoint}; - }; - - const static auto range = [unpack](std::index_sequence) constexpr noexcept -> - std::array - { - return { - {{0x0020, 0x00ff}, // Basic Latin + Latin Supplement - {0x2000, 0x206f}, // General Punctuation - {0x3000, 0x30ff}, // CJK Symbols and Punctuations, Hiragana, Katakana - {0x31f0, 0x31ff}, // Katakana Phonetic Extensions - {0xff00, 0xffef}, // Half-width characters - {0xfffd, 0xfffd}, // Invalid - unpack.operator()()...} - }; - }(std::make_index_sequence{}); - - return range; - } - - [[nodiscard]] auto glyph_range_simplified_chinese_all() noexcept -> glyph_ranges_view_type - { - constexpr static std::array range{{ - {0x0020, 0x00ff}, // Basic Latin + Latin Supplement - {0x2000, 0x206f}, // General Punctuation - {0x3000, 0x30ff}, // CJK Symbols and Punctuations, Hiragana, Katakana - {0x31f0, 0x31ff}, // Katakana Phonetic Extensions - {0xff00, 0xffef}, // Half-width characters - {0xfffd, 0xfffd}, // Invalid - {0x4e00, 0x9faf}, // CJK Ideograms - }}; - - return range; - } - - auto load_font(const std::string_view font_path, const std::uint32_t pixel_height, const glyph_ranges_view_type glyph_ranges) noexcept -> font_type - { - FT_Library ft_library; - if (FT_Init_FreeType(&ft_library)) - { - // Could not initialize FreeType library - return font_type{}; - } - - FT_Face ft_face; - if (FT_New_Face(ft_library, font_path.data(), 0, &ft_face)) - { - FT_Done_FreeType(ft_library); - // Could not load font - return font_type{}; - } - - FT_Set_Pixel_Sizes(ft_face, 0, pixel_height); - - std::vector rects; - for (const auto [first, second]: glyph_ranges) - { - for (auto c = first; c <= second; ++c) - { - if (FT_Load_Char(ft_face, c, FT_LOAD_RENDER)) - { - continue; - } - - const auto& g = ft_face->glyph; - rects.emplace_back( - stbrp_rect - { - .id = std::bit_cast(c), - .w = static_cast(g->bitmap.width), - .h = static_cast(g->bitmap.rows), - .x = static_cast(g->bitmap_left), - .y = static_cast(g->bitmap_top), - .was_packed = 0 - } - ); - } - } - - const auto size = [&rects]() - { - std::uint32_t total_area = 0; - std::uint32_t max_width = 0; - std::uint32_t max_height = 0; - - for (const auto& [id, w, h, x, y, was_packed]: rects) - { - total_area += w * h; - max_width = std::ranges::max(max_width, static_cast(w)); - max_height = std::ranges::max(max_height, static_cast(h)); - } - - const auto min_side = static_cast(std::sqrt(total_area)); - const auto max_side = std::ranges::max(max_width, max_height); - - return std::bit_ceil(std::ranges::max(min_side, max_side)); - }(); - auto atlas_width = size; - auto atlas_height = size; - - stbrp_context context; - std::vector nodes{atlas_width}; - while (true) - { - stbrp_init_target(&context, atlas_width, atlas_height, nodes.data(), static_cast(nodes.size())); - if (stbrp_pack_rects(&context, rects.data(), static_cast(rects.size()))) - { - break; - } - - atlas_width *= 2; - atlas_height *= 2; - nodes.resize(atlas_width); - } - - font_type font - { - .pixel_height = static_cast(pixel_height), - .texture_size = {static_cast(atlas_width), static_cast(atlas_height)}, - .texture_data = std::make_unique_for_overwrite(static_cast(atlas_width * atlas_height)), - .texture_id = 0, - .glyphs = {}, - .fallback_glyph = {} - }; - - for (const auto& [id, rect_width, rect_height, rect_x, rect_y, was_packed]: rects) - { - const auto c = std::bit_cast(id); - - if (FT_Load_Char(ft_face, c, FT_LOAD_RENDER)) - { - continue; - } - - const auto& g = ft_face->glyph; - - for (std::uint32_t y = 0; y < g->bitmap.rows; ++y) - { - for (std::uint32_t x = 0; x < g->bitmap.width; ++x) - { - const auto index = rect_x + x + (rect_y + y) * atlas_width; - font.texture_data[index] = - // A - g->bitmap.buffer[x + y * g->bitmap.pitch] << 24 | - // B - std::uint32_t{255} << 16 | - // G - std::uint32_t{255} << 8 | - // R - std::uint32_t{255}; - } - } - - font.glyphs[c] = { - .rect = { - glyph_type::rect_type::point_type - { - static_cast(g->bitmap_left), - static_cast(g->bitmap_top) - }, - glyph_type::rect_type::extent_type - { - static_cast(g->bitmap.width), - static_cast(g->bitmap.rows) - } - }, - .uv = { - glyph_type::uv_type::point_type - { - static_cast(rect_x) / static_cast(atlas_width), - static_cast(rect_y) / static_cast(atlas_height) - }, - glyph_type::uv_type::extent_type - { - static_cast(g->bitmap.width) / static_cast(atlas_width), - static_cast(g->bitmap.rows) / static_cast(atlas_height) - } - }, - .advance_x = static_cast(g->advance.x) / 64.f - }; - } - - font.fallback_glyph = font.glyphs[U'?']; - - FT_Done_Face(ft_face); - FT_Done_FreeType(ft_library); - - return font; - } -} diff --git a/src/gui/font.ixx b/src/gui/font.ixx deleted file mode 100644 index 07ddea98..00000000 --- a/src/gui/font.ixx +++ /dev/null @@ -1,80 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.gui:font; - -import std; -import gal.prometheus.primitive; - -#else -#pragma once - -#include -#include -#include - -#include -#include - -#endif - -namespace gal::prometheus::gui -{ - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - - using glyph_range_value_type = std::uint32_t; - using glyph_range_type = std::pair; - using glyph_ranges_type = std::vector; - using glyph_ranges_view_type = std::span; - - // Latin - [[nodiscard]] auto glyph_range_latin() noexcept -> glyph_ranges_view_type; - - // Latin + Greek and Coptic - [[nodiscard]] auto glyph_range_greek() noexcept -> glyph_ranges_view_type; - - // Latin + Half-Width + Japanese Hiragana/Katakana + set of 2500 CJK Unified Ideographs for common simplified Chinese - [[nodiscard]] auto glyph_range_simplified_chinese_common() noexcept -> glyph_ranges_view_type; - - // Latin + Half-Width + Japanese Hiragana/Katakana + full set of about 21000 CJK Unified Ideographs - [[nodiscard]] auto glyph_range_simplified_chinese_all() noexcept -> glyph_ranges_view_type; - - struct glyph_type - { - using rect_type = primitive::basic_rect_2d; - using uv_type = primitive::basic_rect_2d; - - rect_type rect; - uv_type uv; - float advance_x; - }; - - struct font_type - { - using extent_type = primitive::basic_extent_2d; - using texture_id_type = std::uintptr_t; - using char_type = char32_t; - using glyph_type = glyph_type; - - float pixel_height; - - extent_type texture_size; - // texture_size.width * texture_size.height (RGBA) - std::unique_ptr texture_data; - texture_id_type texture_id; - - std::unordered_map glyphs; - glyph_type fallback_glyph; - }; - - [[nodiscard]] auto load_font(std::string_view font_path, std::uint32_t pixel_height, glyph_ranges_view_type glyph_ranges) noexcept -> font_type; - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -} diff --git a/src/gui/gui.ixx b/src/gui/gui.ixx deleted file mode 100644 index 1d85b8ee..00000000 --- a/src/gui/gui.ixx +++ /dev/null @@ -1,18 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -export module gal.prometheus.gui; - -export import :font; -export import :draw_list; - -#else -#pragma once - -#include -#include - -#endif diff --git a/src/i18n/i18n.hpp b/src/i18n/i18n.hpp new file mode 100644 index 00000000..f25af63f --- /dev/null +++ b/src/i18n/i18n.hpp @@ -0,0 +1,8 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include diff --git a/src/i18n/range.cpp b/src/i18n/range.cpp new file mode 100644 index 00000000..6f521772 --- /dev/null +++ b/src/i18n/range.cpp @@ -0,0 +1,369 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include +#include + +#include + +#include + +namespace +{ + using namespace gal::prometheus; + + using i18n::RangeBuilder; + + // ReSharper disable once CppInconsistentNaming + constexpr RangeBuilder::value_type japanese_common_accumulative_offsets_from_0x4e00[] + { + 0, 1, 2, 4, 1, 1, 1, 1, 2, 1, 3, 3, 2, 2, 1, 5, 3, 5, 7, 5, 6, 1, 2, 1, 7, 2, 6, 3, 1, 8, 1, 1, 4, 1, 1, 18, 2, 11, 2, 6, 2, 1, 2, 1, 5, 1, 2, 1, + 3, 1, 2, 1, 2, 3, 3, 1, 1, 2, 3, 1, 1, 1, 12, 7, 9, 1, 4, 5, 1, 1, 2, 1, 10, 1, 1, 9, 2, 2, 4, 5, 6, 9, 3, 1, 1, 1, 1, 9, 3, 18, 5, 2, 2, 2, 2, 1, + 6, 3, 7, 1, 1, 1, 1, 2, 2, 4, 2, 1, 23, 2, 10, 4, 3, 5, 2, 4, 10, 2, 4, 13, 1, 6, 1, 9, 3, 1, 1, 6, 6, 7, 6, 3, 1, 2, 11, 3, 2, 2, 3, 2, 15, 2, + 2, 5, 4, 3, 6, 4, 1, 2, 5, 2, 12, 16, 6, 13, 9, 13, 2, 1, 1, 7, 16, 4, 7, 1, 19, 1, 5, 1, 2, 2, 7, 7, 8, 2, 6, 5, 4, 9, 18, 7, 4, 5, 9, 13, 11, + 8, 15, 2, 1, 1, 1, 2, 1, 2, 2, 1, 2, 2, 8, 2, 9, 3, 3, 1, 1, 4, 4, 1, 1, 1, 4, 9, 1, 4, 3, 5, 5, 2, 7, 5, 3, 4, 8, 2, 1, 13, 2, 3, 3, 1, 14, 1, 1, + 4, 5, 1, 3, 6, 1, 5, 2, 1, 1, 3, 3, 3, 3, 1, 1, 2, 7, 6, 6, 7, 1, 4, 7, 6, 1, 1, 1, 1, 1, 12, 3, 3, 9, 5, 2, 6, 1, 5, 6, 1, 2, 3, 18, 2, 4, 14, 4, + 1, 3, 6, 1, 1, 6, 3, 5, 5, 3, 2, 2, 2, 2, 12, 3, 1, 4, 2, 3, 2, 3, 11, 1, 7, 4, 1, 2, 1, 3, 17, 1, 9, 1, 24, 1, 1, 4, 2, 2, 4, 1, 2, 7, 1, 1, 1, 3, + 1, 2, 2, 4, 15, 1, 1, 2, 1, 1, 2, 1, 5, 2, 5, 20, 2, 5, 9, 1, 10, 8, 7, 6, 1, 1, 1, 1, 1, 1, 6, 2, 1, 2, 8, 1, 1, 1, 1, 5, 1, 1, 3, 1, 1, 1, 1, 3, 1, + 1, 12, 4, 1, 3, 1, 1, 1, 1, 1, 10, 3, 1, 7, 5, 13, 1, 2, 3, 4, 6, 1, 1, 30, 2, 9, 9, 1, 15, 38, 11, 3, 1, 8, 24, 7, 1, 9, 8, 10, 2, 1, 9, 31, 2, + 13, 6, 2, 9, 4, 49, 5, 2, 15, 2, 1, 10, 2, 1, 1, 1, 2, 2, 6, 15, 30, 35, 3, 14, 18, 8, 1, 16, 10, 28, 12, 19, 45, 38, 1, 3, 2, 3, 13, 2, 1, 7, + 3, 6, 5, 3, 4, 3, 1, 5, 7, 8, 1, 5, 3, 18, 5, 3, 6, 1, 21, 4, 24, 9, 24, 40, 3, 14, 3, 21, 3, 2, 1, 2, 4, 2, 3, 1, 15, 15, 6, 5, 1, 1, 3, 1, 5, 6, + 1, 9, 7, 3, 3, 2, 1, 4, 3, 8, 21, 5, 16, 4, 5, 2, 10, 11, 11, 3, 6, 3, 2, 9, 3, 6, 13, 1, 2, 1, 1, 1, 1, 11, 12, 6, 6, 1, 4, 2, 6, 5, 2, 1, 1, 3, 3, + 6, 13, 3, 1, 1, 5, 1, 2, 3, 3, 14, 2, 1, 2, 2, 2, 5, 1, 9, 5, 1, 1, 6, 12, 3, 12, 3, 4, 13, 2, 14, 2, 8, 1, 17, 5, 1, 16, 4, 2, 2, 21, 8, 9, 6, 23, + 20, 12, 25, 19, 9, 38, 8, 3, 21, 40, 25, 33, 13, 4, 3, 1, 4, 1, 2, 4, 1, 2, 5, 26, 2, 1, 1, 2, 1, 3, 6, 2, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 9, 2, + 3, 1, 1, 1, 3, 6, 3, 2, 1, 1, 6, 6, 1, 8, 2, 2, 2, 1, 4, 1, 2, 3, 2, 7, 3, 2, 4, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 3, 1, 2, 5, 4, 10, 9, 4, 9, 1, 1, 1, 1, + 1, 1, 5, 3, 2, 1, 6, 4, 9, 6, 1, 10, 2, 31, 17, 8, 3, 7, 5, 40, 1, 7, 7, 1, 6, 5, 2, 10, 7, 8, 4, 15, 39, 25, 6, 28, 47, 18, 10, 7, 1, 3, 1, + 1, 2, 1, 1, 1, 3, 3, 3, 1, 1, 1, 3, 4, 2, 1, 4, 1, 3, 6, 10, 7, 8, 6, 2, 2, 1, 3, 3, 2, 5, 8, 7, 9, 12, 2, 15, 1, 1, 4, 1, 2, 1, 1, 1, 3, 2, 1, 3, 3, + 5, 6, 2, 3, 2, 10, 1, 4, 2, 8, 1, 1, 1, 11, 6, 1, 21, 4, 16, 3, 1, 3, 1, 4, 2, 3, 6, 5, 1, 3, 1, 1, 3, 3, 4, 6, 1, 1, 10, 4, 2, 7, 10, 4, 7, 4, 2, 9, + 4, 3, 1, 1, 1, 4, 1, 8, 3, 4, 1, 3, 1, 6, 1, 4, 2, 1, 4, 7, 2, 1, 8, 1, 4, 5, 1, 1, 2, 2, 4, 6, 2, 7, 1, 10, 1, 1, 3, 4, 11, 10, 8, 21, 4, 6, 1, 3, 5, + 2, 1, 2, 28, 5, 5, 2, 3, 13, 1, 2, 3, 1, 4, 2, 1, 5, 20, 3, 8, 11, 1, 3, 3, 3, 1, 8, 10, 9, 2, 10, 9, 2, 3, 1, 1, 2, 4, 1, 8, 3, 6, 1, 7, 8, 6, + 11, 1, 4, 29, 8, 4, 3, 1, 2, 7, 13, 1, 4, 1, 6, 2, 6, 12, 12, 2, 20, 3, 2, 3, 6, 4, 8, 9, 2, 7, 34, 5, 1, 18, 6, 1, 1, 4, 4, 5, 7, 9, 1, 2, 2, 4, 3, + 4, 1, 7, 2, 2, 2, 6, 2, 3, 25, 5, 3, 6, 1, 4, 6, 7, 4, 2, 1, 4, 2, 13, 6, 4, 4, 3, 1, 5, 3, 4, 4, 3, 2, 1, 1, 4, 1, 2, 1, 1, 3, 1, 11, 1, 6, 3, 1, 7, + 3, 6, 2, 8, 8, 6, 9, 3, 4, 11, 3, 2, 10, 12, 2, 5, 11, 1, 6, 4, 5, 3, 1, 8, 5, 4, 6, 6, 3, 5, 1, 1, 3, 2, 1, 2, 2, 6, 17, 12, 1, 10, 1, 6, 12, 1, + 6, 6, 19, 9, 6, 16, 1, 13, 4, 4, 15, 7, 17, 6, 11, 9, 15, 12, 6, 7, 2, 1, 2, 2, 15, 9, 3, 21, 4, 6, 49, 18, 7, 3, 2, 3, 1, 6, 8, 2, 2, 6, 2, 9, 1, + 3, 6, 4, 4, 1, 2, 16, 2, 5, 2, 1, 6, 2, 3, 5, 3, 1, 2, 5, 1, 2, 1, 9, 3, 1, 8, 6, 4, 8, 11, 3, 1, 1, 1, 1, 3, 1, 13, 8, 4, 1, 3, 2, 2, 1, 4, 1, 11, 1, + 5, 2, 1, 5, 2, 5, 8, 6, 1, 1, 7, 4, 3, 8, 3, 2, 7, 2, 1, 5, 1, 5, 2, 4, 7, 6, 2, 8, 5, 1, 11, 4, 5, 3, 6, 18, 1, 2, 13, 3, 3, 1, 21, 1, 1, 4, 1, 4, + 1, 1, 1, 8, 1, 2, 2, 7, 1, 2, 4, 2, 2, 9, 2, 1, 1, 1, 4, 3, 6, 3, 12, 5, 1, 1, 1, 5, 6, 3, 2, 4, 8, 2, 2, 4, 2, 7, 1, 8, 9, 5, 2, 3, 2, 1, 3, 2, 13, + 7, 14, 6, 5, 1, 1, 2, 1, 4, 2, 23, 2, 1, 1, 6, 3, 1, 4, 1, 15, 3, 1, 7, 3, 9, 14, 1, 3, 1, 4, 1, 1, 5, 8, 1, 3, 8, 3, 8, 15, 11, 4, 14, 4, 4, 2, 5, 5, + 1, 7, 1, 6, 14, 7, 7, 8, 5, 15, 4, 8, 6, 5, 6, 2, 1, 13, 1, 20, 15, 11, 9, 2, 5, 6, 2, 11, 2, 6, 2, 5, 1, 5, 8, 4, 13, 19, 25, 4, 1, 1, 11, 1, 34, + 2, 5, 9, 14, 6, 2, 2, 6, 1, 1, 14, 1, 3, 14, 13, 1, 6, 12, 21, 14, 14, 6, 32, 17, 8, 32, 9, 28, 1, 2, 4, 11, 8, 3, 1, 14, 2, 5, 15, 1, 1, 1, 1, 3, + 6, 4, 1, 3, 4, 11, 3, 1, 1, 11, 30, 1, 5, 1, 4, 1, 5, 8, 1, 1, 3, 2, 4, 3, 17, 35, 2, 6, 12, 17, 3, 1, 6, 2, 1, 1, 12, 2, 7, 3, 3, 2, 1, 16, 2, 8, + 3, 6, 5, 4, 7, 3, 3, 8, 1, 9, 8, 5, 1, 2, 1, 3, 2, 8, 1, 2, 9, 12, 1, 1, 2, 3, 8, 3, 24, 12, 4, 3, 7, 5, 8, 3, 3, 3, 3, 3, 3, 1, 23, 10, 3, 1, 2, 2, + 6, 3, 1, 16, 1, 16, 22, 3, 10, 4, 11, 6, 9, 7, 7, 3, 6, 2, 2, 2, 4, 10, 2, 1, 1, 2, 8, 7, 1, 6, 4, 1, 3, 3, 3, 5, 10, 12, 12, 2, 3, 12, 8, 15, 1, + 1, 16, 6, 6, 1, 5, 9, 11, 4, 11, 4, 2, 6, 12, 1, 17, 5, 13, 1, 4, 9, 5, 1, 11, 2, 1, 8, 1, 5, 7, 28, 8, 3, 5, 10, 2, 17, 3, 38, 22, 1, 2, 18, + 12, 10, 4, 38, 18, 1, 4, 44, 19, 4, 1, 8, 4, 1, 12, 1, 4, 31, 12, 1, 14, 7, 75, 7, 5, 10, 6, 6, 13, 3, 2, 11, 11, 3, 2, 5, 28, 15, 6, 18, 18, + 5, 6, 4, 3, 16, 1, 7, 18, 7, 36, 3, 5, 3, 1, 7, 1, 9, 1, 10, 7, 2, 4, 2, 6, 2, 9, 7, 4, 3, 32, 12, 3, 7, 10, 2, 23, 16, 3, 1, 12, 3, 31, 4, 11, 1, + 3, 8, 9, 5, 1, 30, 15, 6, 12, 3, 2, 2, 11, 19, 9, 14, 2, 6, 2, 3, 19, 13, 17, 5, 3, 3, 25, 3, 14, 1, 1, 1, 36, 1, 3, 2, 19, 3, 13, 36, 9, 13, + 31, 6, 4, 16, 34, 2, 5, 4, 2, 3, 3, 5, 1, 1, 1, 4, 3, 1, 17, 3, 2, 3, 5, 3, 1, 3, 2, 3, 5, 6, 3, 12, 11, 1, 3, 1, 2, 26, 7, 12, 7, 2, 14, 3, 3, + 7, 7, 11, 25, 25, 28, 16, 4, 36, 1, 2, 1, 6, 2, 1, 9, 3, 27, 17, 4, 3, 4, 13, 4, 1, 3, 2, 2, 1, 10, 4, 2, 4, 6, 3, 8, 2, 1, 18, 1, 1, 24, 2, 2, 4, + 33, 2, 3, 63, 7, 1, 6, 40, 7, 3, 4, 4, 2, 4, 15, 18, 1, 16, 1, 1, 11, 2, 41, 14, 1, 3, 18, 13, 3, 2, 4, 16, 2, 17, 7, 15, 24, 7, 18, 13, 44, + 2, 2, 3, 6, 1, 1, 7, 5, 1, 7, 1, 4, 3, 3, 5, 10, 8, 2, 3, 1, 8, 1, 1, 27, 4, 2, 1, 12, 1, 2, 1, 10, 6, 1, 6, 7, 5, 2, 3, 7, 11, 5, 11, 3, 6, 6, 2, 3, + 15, 4, 9, 1, 1, 2, 1, 2, 11, 2, 8, 12, 8, 5, 4, 2, 3, 1, 5, 2, 2, 1, 14, 1, 12, 11, 4, 1, 11, 17, 17, 4, 3, 2, 5, 5, 7, 3, 1, 5, 9, 9, 8, 2, 5, 6, 6, + 13, 13, 2, 1, 2, 6, 1, 2, 2, 49, 4, 9, 1, 2, 10, 16, 7, 8, 4, 3, 2, 23, 4, 58, 3, 29, 1, 14, 19, 19, 11, 11, 2, 7, 5, 1, 3, 4, 6, 2, 18, 5, 12, + 12, 17, 17, 3, 3, 2, 4, 1, 6, 2, 3, 4, 3, 1, 1, 1, 1, 5, 1, 1, 9, 1, 3, 1, 3, 6, 1, 8, 1, 1, 2, 6, 4, 14, 3, 1, 4, 11, 4, 1, 3, 32, 1, 2, 4, 13, 4, + 1, 2, 4, 2, 1, 3, 1, 11, 1, 4, 2, 1, 4, 4, 6, 3, 5, 1, 6, 5, 7, 6, 3, 23, 3, 5, 3, 5, 3, 3, 13, 3, 9, 10, 1, 12, 10, 2, 3, 18, 13, 7, 160, 52, 4, 2, + 2, 3, 2, 14, 5, 4, 12, 4, 6, 4, 1, 20, 4, 11, 6, 2, 12, 27, 1, 4, 1, 2, 2, 7, 4, 5, 2, 28, 3, 7, 25, 8, 3, 19, 3, 6, 10, 2, 2, 1, 10, 2, 5, 4, 1, + 3, 4, 1, 5, 3, 2, 6, 9, 3, 6, 2, 16, 3, 3, 16, 4, 5, 5, 3, 2, 1, 2, 16, 15, 8, 2, 6, 21, 2, 4, 1, 22, 5, 8, 1, 1, 21, 11, 2, 1, 11, 11, 19, 13, + 12, 4, 2, 3, 2, 3, 6, 1, 8, 11, 1, 4, 2, 9, 5, 2, 1, 11, 2, 9, 1, 1, 2, 14, 31, 9, 3, 4, 21, 14, 4, 8, 1, 7, 2, 2, 2, 5, 1, 4, 20, 3, 3, 4, 10, 1, + 11, 9, 8, 2, 1, 4, 5, 14, 12, 14, 2, 17, 9, 6, 31, 4, 14, 1, 20, 13, 26, 5, 2, 7, 3, 6, 13, 2, 4, 2, 19, 6, 2, 2, 18, 9, 3, 5, 12, 12, 14, + 4, 6, 2, 3, 6, 9, 5, 22, 4, 5, 25, 6, 4, 8, 5, 2, 6, 27, 2, 35, 2, 16, 3, 7, 8, 8, 6, 6, 5, 9, 17, 2, 20, 6, 19, 2, 13, 3, 1, 1, 1, 4, 17, 12, + 2, 14, 7, 1, 4, 18, 12, 38, 33, 2, 10, 1, 1, 2, 13, 14, 17, 11, 50, 6, 33, 20, 26, 74, 16, 23, 45, 50, 13, 38, 33, 6, 6, 7, 4, 4, 2, 1, 3, 2, + 5, 8, 7, 8, 9, 3, 11, 21, 9, 13, 1, 3, 10, 6, 7, 1, 2, 2, 18, 5, 5, 1, 9, 9, 2, 68, 9, 19, 13, 2, 5, 1, 4, 4, 7, 4, 13, 3, 9, 10, 21, 17, 3, 26, 2, + 1, 5, 2, 4, 5, 4, 1, 7, 4, 7, 3, 4, 2, 1, 6, 1, 1, 20, 4, 1, 9, 2, 2, 1, 3, 3, 2, 3, 2, 1, 1, 1, 20, 2, 3, 1, 6, 2, 3, 6, 2, 4, 8, 1, 3, 2, 10, 3, 5, 3, + 4, 4, 3, 4, 16, 1, 6, 1, 10, 2, 4, 2, 1, 1, 2, 10, 11, 2, 2, 3, 1, 24, 31, 4, 10, 10, 2, 5, 12, 16, 164, 15, 4, 16, 7, 9, 15, 19, 17, 1, 2, 1, 1, + 5, 1, 1, 1, 1, 1, 3, 1, 4, 3, 1, 3, 1, 3, 1, 2, 1, 1, 3, 3, 7, 2, 8, 1, 2, 2, 2, 1, 3, 4, 3, 7, 8, 12, 92, 2, 10, 3, 1, 3, 14, 5, 25, 16, 42, 4, 7, 7, + 4, 2, 21, 5, 27, 26, 27, 21, 25, 30, 31, 2, 1, 5, 13, 3, 22, 5, 6, 6, 11, 9, 12, 1, 5, 9, 7, 5, 5, 22, 60, 3, 5, 13, 1, 1, 8, 1, 1, 3, 3, 2, 1, 9, + 3, 3, 18, 4, 1, 2, 3, 7, 6, 3, 1, 2, 3, 9, 1, 3, 1, 3, 2, 1, 3, 1, 1, 1, 2, 1, 11, 3, 1, 6, 9, 1, 3, 2, 3, 1, 2, 1, 5, 1, 1, 4, 3, 4, 1, 2, 2, 4, 4, 1, 7, + 2, 1, 2, 2, 3, 5, 13, 18, 3, 4, 14, 9, 9, 4, 16, 3, 7, 5, 8, 2, 6, 48, 28, 3, 1, 1, 4, 2, 14, 8, 2, 9, 2, 1, 15, 2, 4, 3, 2, 10, 16, 12, 8, 7, 1, 1, + 3, 1, 1, 1, 2, 7, 4, 1, 6, 4, 38, 39, 16, 23, 7, 15, 15, 3, 2, 12, 7, 21, 37, 27, 6, 5, 4, 8, 2, 10, 8, 8, 6, 5, 1, 2, 1, 3, 24, 1, 16, 17, 9, 23, + 10, 17, 6, 1, 51, 55, 44, 13, 294, 9, 3, 6, 2, 4, 2, 2, 15, 1, 1, 1, 13, 21, 17, 68, 14, 8, 9, 4, 1, 4, 9, 3, 11, 7, 1, 1, 1, 5, 6, 3, 2, 1, 1, 1, 2, + 3, 8, 1, 2, 2, 4, 1, 5, 5, 2, 1, 4, 3, 7, 13, 4, 1, 4, 1, 3, 1, 1, 1, 5, 5, 10, 1, 6, 1, 5, 2, 1, 5, 2, 4, 1, 4, 5, 7, 3, 18, 2, 9, 11, 32, 4, 3, 3, 2, 4, + 7, 11, 16, 9, 11, 8, 13, 38, 32, 8, 4, 2, 1, 1, 2, 1, 2, 4, 4, 1, 1, 1, 4, 1, 21, 3, 11, 1, 16, 1, 1, 6, 1, 3, 2, 4, 9, 8, 57, 7, 44, 1, 3, 3, 13, 3, 10, + 1, 1, 7, 5, 2, 7, 21, 47, 63, 3, 15, 4, 7, 1, 16, 1, 1, 2, 8, 2, 3, 42, 15, 4, 1, 29, 7, 22, 10, 3, 78, 16, 12, 20, 18, 4, 67, 11, 5, 1, 3, 15, 6, 21, + 31, 32, 27, 18, 13, 71, 35, 5, 142, 4, 10, 1, 2, 50, 19, 33, 16, 35, 37, 16, 19, 27, 7, 1, 133, 19, 1, 4, 8, 7, 20, 1, 4, 4, 1, 10, 3, 1, 6, 1, + 2, 51, 5, 40, 15, 24, 43, 22928, 11, 1, 13, 154, 70, 3, 1, 1, 7, 4, 10, 1, 2, 1, 1, 2, 1, 2, 1, 2, 2, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 3, 2, 1, 1, 1, 1, 2, 1, 1 + }; + + // ReSharper disable once CppInconsistentNaming + constexpr RangeBuilder::value_type simplified_chinese_common_accumulative_offsets_from_0x4e00[] + { + 0, 1, 2, 4, 1, 1, 1, 1, 2, 1, 3, 2, 1, 2, 2, 1, 1, 1, 1, 1, 5, 2, 1, 2, 3, 3, 3, 2, 2, 4, 1, 1, 1, 2, 1, 5, 2, 3, 1, 2, 1, 2, + 1, 1, 2, 1, 1, 2, 2, 1, 4, 1, 1, 1, 1, 5, 10, 1, 2, 19, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 5, 1, 6, 3, 2, 1, 2, 2, 1, 1, 1, 4, 8, 5, + 1, 1, 4, 1, 1, 3, 1, 2, 1, 5, 1, 2, 1, 1, 1, 10, 1, 1, 5, 2, 4, 6, 1, 4, 2, 2, 2, 12, 2, 1, 1, 6, 1, 1, 1, 4, 1, 1, 4, 6, 5, 1, + 4, 2, 2, 4, 10, 7, 1, 1, 4, 2, 4, 2, 1, 4, 3, 6, 10, 12, 5, 7, 2, 14, 2, 9, 1, 1, 6, 7, 10, 4, 7, 13, 1, 5, 4, 8, 4, 1, 1, 2, 28, 5, + 6, 1, 1, 5, 2, 5, 20, 2, 2, 9, 8, 11, 2, 9, 17, 1, 8, 6, 8, 27, 4, 6, 9, 20, 11, 27, 6, 68, 2, 2, 1, 1, 1, 2, 1, 2, 2, 7, 6, 11, 3, 3, + 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, 3, 1, 1, 8, 3, 4, 1, 5, 7, 2, 1, 4, 4, 8, 4, 2, 1, 2, 1, 1, 4, 5, 6, 3, 6, 2, 12, 3, 1, 3, 9, 2, + 4, 3, 4, 1, 5, 3, 3, 1, 3, 7, 1, 5, 1, 1, 1, 1, 2, 3, 4, 5, 2, 3, 2, 6, 1, 1, 2, 1, 7, 1, 7, 3, 4, 5, 15, 2, 2, 1, 5, 3, 22, 19, + 2, 1, 1, 1, 1, 2, 5, 1, 1, 1, 6, 1, 1, 12, 8, 2, 9, 18, 22, 4, 1, 1, 5, 1, 16, 1, 2, 7, 10, 15, 1, 1, 6, 2, 4, 1, 2, 4, 1, 6, 1, 1, + 3, 2, 4, 1, 6, 4, 5, 1, 2, 1, 1, 2, 1, 10, 3, 1, 3, 2, 1, 9, 3, 2, 5, 7, 2, 19, 4, 3, 6, 1, 1, 1, 1, 1, 4, 3, 2, 1, 1, 1, 2, 5, + 3, 1, 1, 1, 2, 2, 1, 1, 2, 1, 1, 2, 1, 3, 1, 1, 1, 3, 7, 1, 4, 1, 1, 2, 1, 1, 2, 1, 2, 4, 4, 3, 8, 1, 1, 1, 2, 1, 3, 5, 1, 3, + 1, 3, 4, 6, 2, 2, 14, 4, 6, 6, 11, 9, 1, 15, 3, 1, 28, 5, 2, 5, 5, 3, 1, 3, 4, 5, 4, 6, 14, 3, 2, 3, 5, 21, 2, 7, 20, 10, 1, 2, 19, 2, + 4, 28, 28, 2, 3, 2, 1, 14, 4, 1, 26, 28, 42, 12, 40, 3, 52, 79, 5, 14, 17, 3, 2, 2, 11, 3, 4, 6, 3, 1, 8, 2, 23, 4, 5, 8, 10, 4, 2, 7, 3, 5, + 1, 1, 6, 3, 1, 2, 2, 2, 5, 28, 1, 1, 7, 7, 20, 5, 3, 29, 3, 17, 26, 1, 8, 4, 27, 3, 6, 11, 23, 5, 3, 4, 6, 13, 24, 16, 6, 5, 10, 25, 35, 7, + 3, 2, 3, 3, 14, 3, 6, 2, 6, 1, 4, 2, 3, 8, 2, 1, 1, 3, 3, 3, 4, 1, 1, 13, 2, 2, 4, 5, 2, 1, 14, 14, 1, 2, 2, 1, 4, 5, 2, 3, 1, 14, + 3, 12, 3, 17, 2, 16, 5, 1, 2, 1, 8, 9, 3, 19, 4, 2, 2, 4, 17, 25, 21, 20, 28, 75, 1, 10, 29, 103, 4, 1, 2, 1, 1, 4, 2, 4, 1, 2, 3, 24, 2, 2, + 2, 1, 1, 2, 1, 3, 8, 1, 1, 1, 2, 1, 1, 3, 1, 1, 1, 6, 1, 5, 3, 1, 1, 1, 3, 4, 1, 1, 5, 2, 1, 5, 6, 13, 9, 16, 1, 1, 1, 1, 3, 2, + 3, 2, 4, 5, 2, 5, 2, 2, 3, 7, 13, 7, 2, 2, 1, 1, 1, 1, 2, 3, 3, 2, 1, 6, 4, 9, 2, 1, 14, 2, 14, 2, 1, 18, 3, 4, 14, 4, 11, 41, 15, 23, + 15, 23, 176, 1, 3, 4, 1, 1, 1, 1, 5, 3, 1, 2, 3, 7, 3, 1, 1, 2, 1, 2, 4, 4, 6, 2, 4, 1, 9, 7, 1, 10, 5, 8, 16, 29, 1, 1, 2, 2, 3, 1, + 3, 5, 2, 4, 5, 4, 1, 1, 2, 2, 3, 3, 7, 1, 6, 10, 1, 17, 1, 44, 4, 6, 2, 1, 1, 6, 5, 4, 2, 10, 1, 6, 9, 2, 8, 1, 24, 1, 2, 13, 7, 8, + 8, 2, 1, 4, 1, 3, 1, 3, 3, 5, 2, 5, 10, 9, 4, 9, 12, 2, 1, 6, 1, 10, 1, 1, 7, 7, 4, 10, 8, 3, 1, 13, 4, 3, 1, 6, 1, 3, 5, 2, 1, 2, + 17, 16, 5, 2, 16, 6, 1, 4, 2, 1, 3, 3, 6, 8, 5, 11, 11, 1, 3, 3, 2, 4, 6, 10, 9, 5, 7, 4, 7, 4, 7, 1, 1, 4, 2, 1, 3, 6, 8, 7, 1, 6, + 11, 5, 5, 3, 24, 9, 4, 2, 7, 13, 5, 1, 8, 82, 16, 61, 1, 1, 1, 4, 2, 2, 16, 10, 3, 8, 1, 1, 6, 4, 2, 1, 3, 1, 1, 1, 4, 3, 8, 4, 2, 2, + 1, 1, 1, 1, 1, 6, 3, 5, 1, 1, 4, 6, 9, 2, 1, 1, 1, 2, 1, 7, 2, 1, 6, 1, 5, 4, 4, 3, 1, 8, 1, 3, 3, 1, 3, 2, 2, 2, 2, 3, 1, 6, + 1, 2, 1, 2, 1, 3, 7, 1, 8, 2, 1, 2, 1, 5, 2, 5, 3, 5, 10, 1, 2, 1, 1, 3, 2, 5, 11, 3, 9, 3, 5, 1, 1, 5, 9, 1, 2, 1, 5, 7, 9, 9, + 8, 1, 3, 3, 3, 6, 8, 2, 3, 2, 1, 1, 32, 6, 1, 2, 15, 9, 3, 7, 13, 1, 3, 10, 13, 2, 14, 1, 13, 10, 2, 1, 3, 10, 4, 15, 2, 15, 15, 10, 1, 3, + 9, 6, 9, 32, 25, 26, 47, 7, 3, 2, 3, 1, 6, 3, 4, 3, 2, 8, 5, 4, 1, 9, 4, 2, 2, 19, 10, 6, 2, 3, 8, 1, 2, 2, 4, 2, 1, 9, 4, 4, 4, 6, + 4, 8, 9, 2, 3, 1, 1, 1, 1, 3, 5, 5, 1, 3, 8, 4, 6, 2, 1, 4, 12, 1, 5, 3, 7, 13, 2, 5, 8, 1, 6, 1, 2, 5, 14, 6, 1, 5, 2, 4, 8, 15, + 5, 1, 23, 6, 62, 2, 10, 1, 1, 8, 1, 2, 2, 10, 4, 2, 2, 9, 2, 1, 1, 3, 2, 3, 1, 5, 3, 3, 2, 1, 3, 8, 1, 1, 1, 11, 3, 1, 1, 4, 3, 7, + 1, 14, 1, 2, 3, 12, 5, 2, 5, 1, 6, 7, 5, 7, 14, 11, 1, 3, 1, 8, 9, 12, 2, 1, 11, 8, 4, 4, 2, 6, 10, 9, 13, 1, 1, 3, 1, 5, 1, 3, 2, 4, + 4, 1, 18, 2, 3, 14, 11, 4, 29, 4, 2, 7, 1, 3, 13, 9, 2, 2, 5, 3, 5, 20, 7, 16, 8, 5, 72, 34, 6, 4, 22, 12, 12, 28, 45, 36, 9, 7, 39, 9, 191, 1, + 1, 1, 4, 11, 8, 4, 9, 2, 3, 22, 1, 1, 1, 1, 4, 17, 1, 7, 7, 1, 11, 31, 10, 2, 4, 8, 2, 3, 2, 1, 4, 2, 16, 4, 32, 2, 3, 19, 13, 4, 9, 1, + 5, 2, 14, 8, 1, 1, 3, 6, 19, 6, 5, 1, 16, 6, 2, 10, 8, 5, 1, 2, 3, 1, 5, 5, 1, 11, 6, 6, 1, 3, 3, 2, 6, 3, 8, 1, 1, 4, 10, 7, 5, 7, + 7, 5, 8, 9, 2, 1, 3, 4, 1, 1, 3, 1, 3, 3, 2, 6, 16, 1, 4, 6, 3, 1, 10, 6, 1, 3, 15, 2, 9, 2, 10, 25, 13, 9, 16, 6, 2, 2, 10, 11, 4, 3, + 9, 1, 2, 6, 6, 5, 4, 30, 40, 1, 10, 7, 12, 14, 33, 6, 3, 6, 7, 3, 1, 3, 1, 11, 14, 4, 9, 5, 12, 11, 49, 18, 51, 31, 140, 31, 2, 2, 1, 5, 1, 8, + 1, 10, 1, 4, 4, 3, 24, 1, 10, 1, 3, 6, 6, 16, 3, 4, 5, 2, 1, 4, 2, 57, 10, 6, 22, 2, 22, 3, 7, 22, 6, 10, 11, 36, 18, 16, 33, 36, 2, 5, 5, 1, + 1, 1, 4, 10, 1, 4, 13, 2, 7, 5, 2, 9, 3, 4, 1, 7, 43, 3, 7, 3, 9, 14, 7, 9, 1, 11, 1, 1, 3, 7, 4, 18, 13, 1, 14, 1, 3, 6, 10, 73, 2, 2, + 30, 6, 1, 11, 18, 19, 13, 22, 3, 46, 42, 37, 89, 7, 3, 16, 34, 2, 2, 3, 9, 1, 7, 1, 1, 1, 2, 2, 4, 10, 7, 3, 10, 3, 9, 5, 28, 9, 2, 6, 13, 7, + 3, 1, 3, 10, 2, 7, 2, 11, 3, 6, 21, 54, 85, 2, 1, 4, 2, 2, 1, 39, 3, 21, 2, 2, 5, 1, 1, 1, 4, 1, 1, 3, 4, 15, 1, 3, 2, 4, 4, 2, 3, 8, + 2, 20, 1, 8, 7, 13, 4, 1, 26, 6, 2, 9, 34, 4, 21, 52, 10, 4, 4, 1, 5, 12, 2, 11, 1, 7, 2, 30, 12, 44, 2, 30, 1, 1, 3, 6, 16, 9, 17, 39, 82, 2, + 2, 24, 7, 1, 7, 3, 16, 9, 14, 44, 2, 1, 2, 1, 2, 3, 5, 2, 4, 1, 6, 7, 5, 3, 2, 6, 1, 11, 5, 11, 2, 1, 18, 19, 8, 1, 3, 24, 29, 2, 1, 3, + 5, 2, 2, 1, 13, 6, 5, 1, 46, 11, 3, 5, 1, 1, 5, 8, 2, 10, 6, 12, 6, 3, 7, 11, 2, 4, 16, 13, 2, 5, 1, 1, 2, 2, 5, 2, 28, 5, 2, 23, 10, 8, + 4, 4, 22, 39, 95, 38, 8, 14, 9, 5, 1, 13, 5, 4, 3, 13, 12, 11, 1, 9, 1, 27, 37, 2, 5, 4, 4, 63, 211, 95, 2, 2, 2, 1, 3, 5, 2, 1, 1, 2, 2, 1, + 1, 1, 3, 2, 4, 1, 2, 1, 1, 5, 2, 2, 1, 1, 2, 3, 1, 3, 1, 1, 1, 3, 1, 4, 2, 1, 3, 6, 1, 1, 3, 7, 15, 5, 3, 2, 5, 3, 9, 11, 4, 2, + 22, 1, 6, 3, 8, 7, 1, 4, 28, 4, 16, 3, 3, 25, 4, 4, 27, 27, 1, 4, 1, 2, 2, 7, 1, 3, 5, 2, 28, 8, 2, 14, 1, 8, 6, 16, 25, 3, 3, 3, 14, 3, + 3, 1, 1, 2, 1, 4, 6, 3, 8, 4, 1, 1, 1, 2, 3, 6, 10, 6, 2, 3, 18, 3, 2, 5, 5, 4, 3, 1, 5, 2, 5, 4, 23, 7, 6, 12, 6, 4, 17, 11, 9, 5, + 1, 1, 10, 5, 12, 1, 1, 11, 26, 33, 7, 3, 6, 1, 17, 7, 1, 5, 12, 1, 11, 2, 4, 1, 8, 14, 17, 23, 1, 2, 1, 7, 8, 16, 11, 9, 6, 5, 2, 6, 4, 16, + 2, 8, 14, 1, 11, 8, 9, 1, 1, 1, 9, 25, 4, 11, 19, 7, 2, 15, 2, 12, 8, 52, 7, 5, 19, 2, 16, 4, 36, 8, 1, 16, 8, 24, 26, 4, 6, 2, 9, 5, 4, 36, + 3, 28, 12, 25, 15, 37, 27, 17, 12, 59, 38, 5, 32, 127, 1, 2, 9, 17, 14, 4, 1, 2, 1, 1, 8, 11, 50, 4, 14, 2, 19, 16, 4, 17, 5, 4, 5, 26, 12, 45, 2, 23, + 45, 104, 30, 12, 8, 3, 10, 2, 2, 3, 3, 1, 4, 20, 7, 2, 9, 6, 15, 2, 20, 1, 3, 16, 4, 11, 15, 6, 134, 2, 5, 59, 1, 2, 2, 2, 1, 9, 17, 3, 26, 137, + 10, 211, 59, 1, 2, 4, 1, 4, 1, 1, 1, 2, 6, 2, 3, 1, 1, 2, 3, 2, 3, 1, 3, 4, 4, 2, 3, 3, 1, 4, 3, 1, 7, 2, 2, 3, 1, 2, 1, 3, 3, 3, + 2, 2, 3, 2, 1, 3, 14, 6, 1, 3, 2, 9, 6, 15, 27, 9, 34, 145, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 2, 2, 3, 1, 2, 1, 1, 1, 2, 3, 5, + 8, 3, 5, 2, 4, 1, 3, 2, 2, 2, 12, 4, 1, 1, 1, 10, 4, 5, 1, 20, 4, 16, 1, 15, 9, 5, 12, 2, 9, 2, 5, 4, 2, 26, 19, 7, 1, 26, 4, 30, 12, 15, + 42, 1, 6, 8, 172, 1, 1, 4, 2, 1, 1, 11, 2, 2, 4, 2, 1, 2, 1, 10, 8, 1, 2, 1, 4, 5, 1, 2, 5, 1, 8, 4, 1, 3, 4, 2, 1, 6, 2, 1, 3, 4, + 1, 2, 1, 1, 1, 1, 12, 5, 7, 2, 4, 3, 1, 1, 1, 3, 3, 6, 1, 2, 2, 3, 3, 3, 2, 1, 2, 12, 14, 11, 6, 6, 4, 12, 2, 8, 1, 7, 10, 1, 35, 7, + 4, 13, 15, 4, 3, 23, 21, 28, 52, 5, 26, 5, 6, 1, 7, 10, 2, 7, 53, 3, 2, 1, 1, 1, 2, 163, 532, 1, 10, 11, 1, 3, 3, 4, 8, 2, 8, 6, 2, 2, 23, 22, + 4, 2, 2, 4, 2, 1, 3, 1, 3, 3, 5, 9, 8, 2, 1, 2, 8, 1, 10, 2, 12, 21, 20, 15, 105, 2, 3, 1, 1, 3, 2, 3, 1, 1, 2, 5, 1, 4, 15, 11, 19, 1, + 1, 1, 1, 5, 4, 5, 1, 1, 2, 5, 3, 5, 12, 1, 2, 5, 1, 11, 1, 1, 15, 9, 1, 4, 5, 3, 26, 8, 2, 1, 3, 1, 1, 15, 19, 2, 12, 1, 2, 5, 2, 7, + 2, 19, 2, 20, 6, 26, 7, 5, 2, 2, 7, 34, 21, 13, 70, 2, 128, 1, 1, 2, 1, 1, 2, 1, 1, 3, 2, 2, 2, 15, 1, 4, 1, 3, 4, 42, 10, 6, 1, 49, 85, 8, + 1, 2, 1, 1, 4, 4, 2, 3, 6, 1, 5, 7, 4, 3, 211, 4, 1, 2, 1, 2, 5, 1, 2, 4, 2, 2, 6, 5, 6, 10, 3, 4, 48, 100, 6, 2, 16, 296, 5, 27, 387, 2, + 2, 3, 7, 16, 8, 5, 38, 15, 39, 21, 9, 10, 3, 7, 59, 13, 27, 21, 47, 5, 21, 6 + }; + + constexpr auto unpack_accumulative_offsets_range(RangeBuilder::ranges_type& ranges, const auto& accumulative_offsets_range) noexcept -> void + { + using range_type = RangeBuilder::range_type; + using value_type = RangeBuilder::value_type; + + // const auto unpack = [&accumulative_offsets_range]() constexpr noexcept -> range_type + const auto unpack = [&accumulative_offsets_range](const auto i) constexpr noexcept -> range_type + { + const auto codepoint = + 0x4e00 + + std::ranges::fold_left( + std::ranges::subrange{ + std::ranges::begin(accumulative_offsets_range), + // std::ranges::begin(accumulative_offsets_range) + 1 + I + std::ranges::begin(accumulative_offsets_range) + 1 + i + }, + value_type{0}, + [](const value_type total, const value_type current) noexcept -> value_type + { + return total + current; + } + ); + return {codepoint, codepoint}; + }; + + // [&ranges, unpack](std::index_sequence) noexcept -> void + // { + // (ranges.emplace_back(unpack.operator()()), ...); + // }(std::make_index_sequence{}); + std::ranges::for_each( + std::views::iota(0ull, std::ranges::size(accumulative_offsets_range)), + [&ranges, unpack](const auto i) noexcept -> void + { + ranges.emplace_back(unpack(i)); + } + ); + } + + constexpr auto get_ascii(RangeBuilder::ranges_type& ranges) noexcept -> void + { + ranges.emplace_back(0x0020, 0x007f); + } + + constexpr auto get_latin(RangeBuilder::ranges_type& ranges) noexcept -> void + { + // Basic Latin + Latin Supplement + ranges.emplace_back(0x0020, 0x00ff); + } + + constexpr auto get_greek_and_coptic(RangeBuilder::ranges_type& ranges) noexcept + { + // Basic Latin + Latin Supplement + get_latin(ranges); + // Greek + Coptic + ranges.emplace_back(0x0370, 0x03ff); + } + + constexpr auto get_korean(RangeBuilder::ranges_type& ranges) noexcept -> void + { + // Basic Latin + Latin Supplement + get_latin(ranges); + // Korean alphabets + ranges.emplace_back(0x3131, 0x3163); + // Korean characters + ranges.emplace_back(0xAC00, 0xD7A3); + // Invalid + ranges.emplace_back(0xFFFD, 0xFFFD); + } + + constexpr auto get_japanese(RangeBuilder::ranges_type& ranges) noexcept -> void + { + // Basic Latin + Latin Supplement + get_latin(ranges); + // General Punctuation + ranges.emplace_back(0x2000, 0x206f); + // CJK Symbols and Punctuations, Hiragana, Katakana + ranges.emplace_back(0x3000, 0x30ff); + // Katakana Phonetic Extensions + ranges.emplace_back(0x31f0, 0x31ff); + // Half-width characters + ranges.emplace_back(0xff00, 0xffef); + // Invalid + ranges.emplace_back(0xfffd, 0xfffd); + + unpack_accumulative_offsets_range(ranges, japanese_common_accumulative_offsets_from_0x4e00); + } + + template + constexpr auto get_simplified_chinese(RangeBuilder::ranges_type& ranges) noexcept -> void + { + // Basic Latin + Latin Supplement + get_latin(ranges); + // General Punctuation + ranges.emplace_back(0x2000, 0x206f); + // CJK Symbols and Punctuations, Hiragana, Katakana + ranges.emplace_back(0x3000, 0x30ff); + // Katakana Phonetic Extensions + ranges.emplace_back(0x31f0, 0x31ff); + // Half-width characters + ranges.emplace_back(0xff00, 0xffef); + // Invalid + ranges.emplace_back(0xfffd, 0xfffd); + + if constexpr (FullSet) + { + ranges.emplace_back(0x4e00, 0x9faf); + } + else + { + unpack_accumulative_offsets_range(ranges, simplified_chinese_common_accumulative_offsets_from_0x4e00); + } + } +} + +// ReSharper disable once CppInconsistentNaming +namespace gal::prometheus::i18n +{ + auto RangeBuilder::ascii() & noexcept -> RangeBuilder& + { + get_ascii(ranges_); + + return *this; + } + + auto RangeBuilder::ascii() && noexcept -> RangeBuilder&& + { + auto& self = *this; + self.ascii(); + return std::move(self); + } + + auto RangeBuilder::latin() & noexcept -> RangeBuilder& + { + get_latin(ranges_); + + return *this; + } + + auto RangeBuilder::latin() && noexcept -> RangeBuilder&& + { + auto& self = *this; + self.latin(); + return std::move(self); + } + + auto RangeBuilder::greek() & noexcept -> RangeBuilder& + { + get_greek_and_coptic(ranges_); + + return *this; + } + + auto RangeBuilder::greek() && noexcept -> RangeBuilder&& + { + auto& self = *this; + self.greek(); + return std::move(self); + } + + auto RangeBuilder::korean() & noexcept -> RangeBuilder& + { + get_korean(ranges_); + + return *this; + } + + auto RangeBuilder::korean() && noexcept -> RangeBuilder&& + { + auto& self = *this; + self.korean(); + return std::move(self); + } + + auto RangeBuilder::japanese() & noexcept -> RangeBuilder& + { + get_japanese(ranges_); + + return *this; + } + + auto RangeBuilder::japanese() && noexcept -> RangeBuilder&& + { + auto& self = *this; + self.japanese(); + return std::move(self); + } + + auto RangeBuilder::simplified_chinese_common() & noexcept -> RangeBuilder& + { + get_simplified_chinese(ranges_); + + return *this; + } + + auto RangeBuilder::simplified_chinese_common() && noexcept -> RangeBuilder&& + { + auto& self = *this; + self.simplified_chinese_common(); + return std::move(self); + } + + auto RangeBuilder::simplified_chinese_all() & noexcept -> RangeBuilder& + { + get_simplified_chinese(ranges_); + + return *this; + } + + auto RangeBuilder::simplified_chinese_all() && noexcept -> RangeBuilder&& + { + auto& self = *this; + self.simplified_chinese_all(); + return std::move(self); + } +} diff --git a/src/i18n/range.hpp b/src/i18n/range.hpp new file mode 100644 index 00000000..84be456f --- /dev/null +++ b/src/i18n/range.hpp @@ -0,0 +1,79 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include + +#include + +// ReSharper disable once CppInconsistentNaming +namespace gal::prometheus::i18n +{ + class RangeBuilder final + { + public: + using value_type = std::uint32_t; + + struct range_type + { + value_type from; + value_type to; + }; + + using ranges_type = std::vector; + + private: + ranges_type ranges_; + + public: + #if defined(__cpp_explicit_this_parameter) and __cpp_explicit_this_parameter >= 202110L + template + [[nodiscard]] auto range(this Self&& self) noexcept -> decltype(auto) + { + return std::forward(self).ranges_; + } + #else + [[nodiscard]] auto range() const & noexcept -> const ranges_type& + { + return ranges_; + } + + [[nodiscard]] auto range() && noexcept -> ranges_type&& + { + return std::move(ranges_); + } + #endif + + // ASCII + auto ascii() & noexcept -> RangeBuilder&; + auto ascii() && noexcept -> RangeBuilder&&; + + // Latin + auto latin() & noexcept -> RangeBuilder&; + auto latin() && noexcept -> RangeBuilder&&; + + // Latin + Greek and Coptic + auto greek() & noexcept -> RangeBuilder&; + auto greek() && noexcept -> RangeBuilder&&; + + // Latin + Korean characters + auto korean() & noexcept -> RangeBuilder&; + auto korean() && noexcept -> RangeBuilder&&; + + // Latin + Hiragana, Katakana, Half-Width, Selection of 2999 Ideographs + auto japanese() & noexcept -> RangeBuilder&; + auto japanese() && noexcept -> RangeBuilder&&; + + // Latin + Half-Width + Japanese Hiragana/Katakana + set of 2500 CJK Unified Ideographs for common simplified Chinese + auto simplified_chinese_common() & noexcept -> RangeBuilder&; + auto simplified_chinese_common() && noexcept -> RangeBuilder&&; + + // Latin + Half-Width + Japanese Hiragana/Katakana + full set of about 21000 CJK Unified Ideographs + auto simplified_chinese_all() & noexcept -> RangeBuilder&; + auto simplified_chinese_all() && noexcept -> RangeBuilder&&; + }; +} diff --git a/src/io/device.ixx b/src/io/device.ixx new file mode 100644 index 00000000..0bca8796 --- /dev/null +++ b/src/io/device.ixx @@ -0,0 +1,365 @@ +// This file is part of prometheus +// Copyright (C) 2022-2024 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#if not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED + +#include + +export module gal.prometheus:io.device; + +import std; + +#if GAL_PROMETHEUS_COMPILER_DEBUG +import :platform; +#endif + +#endif not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED + +#if not GAL_PROMETHEUS_USE_MODULE + +#pragma once + +#include +#include +#include +#include + +#include + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +#endif + +#if GAL_PROMETHEUS_INTELLISENSE_WORKING +namespace GAL_PROMETHEUS_COMPILER_MODULE_NAMESPACE_PREFIX :: io +#else +GAL_PROMETHEUS_COMPILER_MODULE_NAMESPACE_EXPORT(io) +#endif +{ + enum class DeviceKey : std::uint16_t + { + NONE, + + // ======================================== + // keyboard <------------------------------------------------------- + // from left to right, top to bottom + // ======================================== + + KB_ESCAPE, + + // ================== + // function keys + + KB_F1, + KB_F2, + KB_F3, + KB_F4, + KB_F5, + KB_F6, + KB_F7, + KB_F8, + KB_F9, + KB_F10, + KB_F11, + FK_F12, + + // ================== + // main area + + // ` + KB_GRAVE_ACCENT, + KB_1, + KB_2, + KB_3, + KB_4, + KB_5, + KB_6, + KB_7, + KB_8, + KB_9, + KB_0, + // - OR _ + KB_MINUS, + // = OR + + KB_PLUS, + KB_BACKSPACE, + + KB_TAB, + KB_Q, + KB_W, + KB_E, + KB_R, + KB_T, + KB_Y, + KB_U, + KB_I, + KB_O, + KB_P, + // [ OR { + KB_LEFT_BRACKET, + // ] OR } + KB_RIGHT_BRACKET, + // \ OR | + KB_BACKSLASH, + + KB_CAPS_LOCK, + KB_A, + KB_S, + KB_D, + KB_F, + KB_G, + KB_H, + KB_J, + KB_K, + KB_L, + // ; OR : + KB_SEMICOLON, + // ' OR " + KB_QUOTATION, + KB_ENTER, + + KB_LEFT_SHIFT, + KB_Z, + KB_X, + KB_C, + KB_V, + KB_B, + KB_N, + KB_M, + // , OR < + KB_COMMA, + // . OR > + KB_PERIOD, + // / OR ? + KB_SLASH, + KB_RIGHT_SHIFT, + + KB_LEFT_CTRL, + KB_LEFT_SUPER, + KB_LEFT_ALT, + KB_SPACE, + KB_RIGHT_ALT, + KB_RIGHT_SUPER, + KB_MENU, + KB_RIGHT_CTRL, + + // ================== + // navigation keys + + KB_PAUSE, + KB_SCROLL_LOCK, + KB_PRINT_SCREEN, + + KB_INSERT, + KB_HOME, + KB_PAGE_UP, + KB_DELETE, + KB_END, + KB_PAGE_DOWN, + + KB_ARROW_UP, + KB_ARROW_LEFT, + KB_ARROW_DOWN, + KB_ARROW_RIGHT, + + // ================== + // Numeric Keypad + + KB_KEYPAD_NUM_LOCK, + // / + KB_KEYPAD_DIVIDE, + // * + KB_KEYPAD_MULTIPLY, + // - + KB_KEYPAD_MINUS, + + KB_KEYPAD_7, + KB_KEYPAD_8, + KB_KEYPAD_9, + KB_KEYPAD_PLUS, + + KB_KEYPAD_4, + KB_KEYPAD_5, + KB_KEYPAD_6, + + KB_KEYPAD_1, + KB_KEYPAD_2, + KB_KEYPAD_3, + KB_KEYPAD_ENTER, + + KB_KEYPAD_0, + KB_KEYPAD_DECIMAL, + + // ======================================== + // -------------------------------------------------------> keyboard + // ======================================== + + // incoming + }; + + enum class MouseButton : std::uint32_t + { + LEFT = 0, + MIDDLE = 1, + RIGHT = 2, + X1 = 3, + X2 = 4 + }; + + enum class MouseButtonStatus : std::uint32_t + { + PRESS, + RELEASE, + }; + + enum class DeviceActionType : std::uint8_t + { + MOUSE_MOV = 0, + MOUSE_BUTTON = 1, + MOUSE_WHEEL = 2, + + TEXT = 3, + KEY = 4, + }; + + class [[nodiscard]] DeviceEvent + { + public: + struct mouse_mov_type + { + float x; + float y; + }; + + struct mouse_button_type + { + MouseButton button; + MouseButtonStatus status; + }; + + struct mouse_wheel_type + { + float x; + float y; + }; + + struct text_type + { + std::uint32_t c; + std::uint32_t pad; + }; + + struct key_type + { + DeviceKey key; + bool down; + float analog_value; + + static_assert(sizeof(DeviceKey) == 2); + std::uint8_t pad; + }; + + using underlying_type = std::variant; + static_assert(std::is_same_v(DeviceActionType::MOUSE_MOV), underlying_type>, mouse_mov_type>); + static_assert(std::is_same_v(DeviceActionType::MOUSE_BUTTON), underlying_type>, mouse_button_type>); + static_assert(std::is_same_v(DeviceActionType::MOUSE_WHEEL), underlying_type>, mouse_wheel_type>); + static_assert(std::is_same_v(DeviceActionType::TEXT), underlying_type>, text_type>); + static_assert(std::is_same_v(DeviceActionType::KEY), underlying_type>, key_type>); + + private: + underlying_type underlying_; + + public: + template + requires std::is_constructible_v, Args...> + constexpr explicit DeviceEvent(std::in_place_type_t type, Args&&... args) noexcept + : underlying_{type, std::forward(args)...} {} + + template + requires std::is_constructible_v + constexpr explicit DeviceEvent(Args&&... args) noexcept + : underlying_{std::forward(args)...} {} + + [[nodiscard]] constexpr auto type() const noexcept -> DeviceActionType + { + return static_cast(underlying_.index()); + } + + // note: unchecked + template + [[nodiscard]] constexpr auto value() const noexcept -> const auto& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(underlying_.index() == static_cast(Type)); + return std::get(Type)>(underlying_); + } + + template + constexpr auto visit(Callable&& callable) const noexcept -> void + { + std::visit(std::forward(callable), underlying_); + } + }; + + class DeviceEventQueue + { + public: + template + using list_type = std::vector; + + using queue_type = list_type; + using size_type = queue_type::size_type; + + private: + queue_type queue_; + + public: + [[nodiscard]] constexpr auto size() const noexcept -> size_type + { + return queue_.size(); + } + + [[nodiscard]] constexpr auto consume() noexcept -> DeviceEvent + { + const auto e = queue_.back(); + queue_.pop_back(); + return e; + } + + constexpr auto mouse_move(const float x, const float y) noexcept -> void + { + // queue_.emplace_back(std::in_place_type, x, y); + queue_.emplace_back(DeviceEvent::mouse_mov_type{.x = x, .y = y}); + } + + constexpr auto mouse_button(const MouseButton button, const MouseButtonStatus status) noexcept -> void + { + // queue_.emplace_back(std::in_place_type, button, status); + queue_.emplace_back(DeviceEvent::mouse_button_type{.button = button, .status = status}); + } + + constexpr auto mouse_wheel(const float x, const float y) noexcept -> void + { + // queue_.emplace_back(std::in_place_type, x, y); + queue_.emplace_back(DeviceEvent::mouse_wheel_type{.x = x, .y = y}); + } + + constexpr auto text(const std::uint32_t c) noexcept -> void + { + // queue_.emplace_back(std::in_place_type, c); + queue_.emplace_back(DeviceEvent::text_type{.c = c, .pad = 0}); + } + + constexpr auto key(const DeviceKey key, const bool down, const float analog_value) noexcept -> void + { + // queue_.emplace_back(std::in_place_type, key, down, analog_value); + queue_.emplace_back(DeviceEvent::key_type{.key = key, .down = down, .analog_value = analog_value, .pad = 0}); + } + + constexpr auto key(const DeviceKey key, const bool down) noexcept -> void + { + return this->key(key, down, down ? 1.f : 0.f); + } + }; +} diff --git a/src/io/io.ixx b/src/io/io.ixx new file mode 100644 index 00000000..1468b62a --- /dev/null +++ b/src/io/io.ixx @@ -0,0 +1,22 @@ +// This file is part of prometheus +// Copyright (C) 2022-2024 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#if not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED + +#include + +export module gal.prometheus:io; + +export import :io.device; + +#endif not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED + +#if not GAL_PROMETHEUS_USE_MODULE + +#pragma once + +#include + +#endif diff --git a/src/math/cmath.hpp b/src/math/cmath.hpp new file mode 100644 index 00000000..f8a12ea4 --- /dev/null +++ b/src/math/cmath.hpp @@ -0,0 +1,390 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include + +#include + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +#if not defined(__cpp_lib_constexpr_cmath) or __cpp_lib_constexpr_cmath < 202306L +#define CMATH_WORKAROUND_REQUIRED +#endif + +namespace gal::prometheus::math +{ + template + requires std::is_arithmetic_v + [[nodiscard]] constexpr auto is_nan(const T value) noexcept -> bool + { + if constexpr (not std::is_floating_point_v) + { + return false; + } + else + { + #if defined(CMATH_WORKAROUND_REQUIRED) + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED + { + // ReSharper disable once CppIdenticalOperandsInBinaryExpression + return value != value; // NOLINT(misc-redundant-expression) + } + #endif + + return std::isnan(value); + } + } + + template + requires std::is_arithmetic_v + [[nodiscard]] constexpr auto abs(const T value) noexcept -> T + { + if constexpr (std::is_unsigned_v) + { + return value; + } + else + { + #if defined(CMATH_WORKAROUND_REQUIRED) + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED + { + return value > 0 ? value : -value; + } + #endif + + return std::abs(value); + } + } + + template + requires std::is_arithmetic_v + [[nodiscard]] constexpr auto floor(const T value) noexcept -> T + { + if constexpr (not std::is_floating_point_v) + { + return value; + } + else + { + #if defined(CMATH_WORKAROUND_REQUIRED) + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED + { + if (value >= 0 or static_cast(static_cast(value)) == value) + { + return static_cast(static_cast(value)); + } + + return static_cast(static_cast(value) - 1); + } + #endif + + return std::floor(value); + } + } + + template + requires std::is_arithmetic_v + [[nodiscard]] constexpr auto ceil(const T value) noexcept -> T + { + if constexpr (not std::is_floating_point_v) + { + return value; + } + else + { + #if defined(CMATH_WORKAROUND_REQUIRED) + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED + { + if (value >= 0 or static_cast(static_cast(value)) == value) + { + return static_cast(static_cast(value)); + } + + return static_cast(static_cast(value) + 1); + } + #endif + + return std::ceil(value); + } + } + + template + requires std::is_arithmetic_v + // ReSharper disable once IdentifierTypo + [[nodiscard]] constexpr auto tgamma(const T value) noexcept -> T + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(value >= T{0}); + + if constexpr (not std::is_floating_point_v) + { + return value; + } + else + { + #if defined(CMATH_WORKAROUND_REQUIRED) + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED + { + return (value <= 1) ? 1 : (value * math::tgamma(value - 1)); + } + #endif + + return static_cast(std::tgamma(value)); + } + } + + template + [[nodiscard]] constexpr auto factorial(const T value) noexcept -> T // + { + return math::tgamma(value); + } + + template + requires std::is_arithmetic_v + [[nodiscard]] constexpr auto pow(const T base, const int exp) noexcept -> T + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(exp >= 0); + + #if defined(CMATH_WORKAROUND_REQUIRED) + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED + { + if (exp == 0) + { + return static_cast(1); + } + + return static_cast(base * math::pow(base, exp - 1)); + } + #endif + + return static_cast(std::pow(base, exp)); + } + + template + requires std::is_arithmetic_v + [[nodiscard]] constexpr auto sqrt(const T value) noexcept -> T + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(value >= 0); + + #if defined(CMATH_WORKAROUND_REQUIRED) + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED + { + if (value == 0) // NOLINT(clang-diagnostic-float-equal) + { + return value; + } + + auto prev = static_cast(0); + auto current = static_cast(value / 2); + + while (current != prev) // NOLINT(clang-diagnostic-float-equal) + { + prev = current; + current = (current + value / current) / 2; + } + + return current; + } + #endif + + return static_cast(std::sqrt(value)); + } + + template + requires std::is_arithmetic_v + // ReSharper disable once IdentifierTypo + [[nodiscard]] constexpr auto hypot(const T x, const std::type_identity_t y) noexcept -> T + { + #if defined(CMATH_WORKAROUND_REQUIRED) + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED + { + return math::sqrt(math::pow(x, 2) + math::pow(y, 2)); + } + #endif + + return static_cast(std::hypot(x, y)); + } + + template + requires std::is_arithmetic_v + // ReSharper disable once IdentifierTypo + [[nodiscard]] constexpr auto hypot(const T x, const std::type_identity_t y, const std::type_identity_t z) noexcept -> T + { + #if defined(CMATH_WORKAROUND_REQUIRED) + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED + { + return math::sqrt(math::pow(x, 2) + math::pow(y, 2) + math::pow(z, 2)); + } + #endif + + return static_cast(std::hypot(x, y, z)); + } + + template + requires std::is_arithmetic_v + [[nodiscard]] constexpr auto normalize(const T x, const T y) noexcept -> auto + { + using value_type = std::conditional_t; + using result_type = std::pair; + + if (const auto length = math::hypot(static_cast(x), static_cast(y)); + length > std::numeric_limits::epsilon()) + { + return result_type{static_cast(x) / length, static_cast(y) / length}; + } + + return result_type{x, y}; + } + + #if defined(CMATH_WORKAROUND_REQUIRED) + // ReSharper disable once IdentifierTypo + namespace cmath_detail + { + template + [[nodiscard]] constexpr auto tan_series_exp(const T value) noexcept -> T + { + const auto z = value - std::numbers::pi_v / 2; + + if (std::numeric_limits::min() > math::abs(z)) + { + return std::numbers::pi_v / 2; + } + + // this is based on a fourth-order expansion of tan(z) using Bernoulli numbers + return -1 / z + (z / 3 + (math::pow(z, 3) / 45 + (2 * math::pow(z, 5) / 945 + math::pow(z, 7) / 4725))); + } + + template + [[nodiscard]] constexpr auto tan_cf_recurse(const T value, const int current, const int max) noexcept -> T + { + const auto z = static_cast(2 * current - 1); + + if (current < max) { return z - value / cmath_detail::tan_cf_recurse(value, current + 1, max); } + + return z; + } + + template + [[nodiscard]] constexpr auto tan_cf_main(const T value) noexcept -> T + { + if (value > static_cast(1.55) and value < static_cast(1.6)) + { + // deals with a singularity at tan(pi/2) + return cmath_detail::tan_series_exp(value); + } + + if (value > static_cast(1.4)) + { + return value / cmath_detail::tan_cf_recurse(value * value, 1, 45); + } + + if (value > static_cast(1)) + { + return value / cmath_detail::tan_cf_recurse(value * value, 1, 35); + } + + return value / cmath_detail::tan_cf_recurse(value * value, 1, 25); + } + + template + [[nodiscard]] constexpr auto tan_begin(const T value, const int count = 0) noexcept -> T + { + if (value > std::numbers::pi_v) + { + if (count > 1) + { + return std::numeric_limits::quiet_NaN(); + } + + return cmath_detail::tan_begin(value - std::numbers::pi_v * math::floor(value - std::numbers::pi_v), count + 1); + } + + return cmath_detail::tan_cf_main(value); + } + } + #endif + + template + requires std::is_arithmetic_v + [[nodiscard]] constexpr auto tan(const T value) noexcept -> T + { + #if defined(CMATH_WORKAROUND_REQUIRED) + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED + { + if (math::is_nan(value)) + { + return std::numeric_limits::quiet_NaN(); + } + + if (value < static_cast(0)) + { + return -cmath_detail::tan_begin(-value); + } + + return cmath_detail::tan_begin(value); + } + #endif + + return std::tan(value); + } + + template + requires std::is_arithmetic_v + [[nodiscard]] constexpr auto sin(const T value) noexcept -> T + { + #if defined(CMATH_WORKAROUND_REQUIRED) + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED + { + if (math::is_nan(value)) { return std::numeric_limits::quiet_NaN(); } + + if (std::numeric_limits::min() > math::abs(value - std::numbers::pi_v / 2)) { return 1; } + + if (std::numeric_limits::min() > math::abs(value + std::numbers::pi_v / 2)) { return -1; } + + if (std::numeric_limits::min() > math::abs(value - std::numbers::pi_v)) { return 0; } + + if (std::numeric_limits::min() > math::abs(value + std::numbers::pi_v)) { return -0; } + + // sin(x) = 2tan(x/2) / (1 + tan²(x/2)) + const auto z = math::tan(value / static_cast(2)); + return (static_cast(2) * z) / (static_cast(1) + z * z); + } + #endif + + return static_cast(std::sin(value)); + } + + template + requires std::is_arithmetic_v + [[nodiscard]] constexpr auto cos(const T value) noexcept -> T + { + #if defined(CMATH_WORKAROUND_REQUIRED) + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED + { + if (math::is_nan(value)) { return std::numeric_limits::quiet_NaN(); } + + if (std::numeric_limits::min() > math::abs(value - std::numbers::pi_v / 2)) { return 0; } + + if (std::numeric_limits::min() > math::abs(value + std::numbers::pi_v / 2)) { return -0; } + + if (std::numeric_limits::min() > math::abs(value - std::numbers::pi_v)) { return -1; } + + if (std::numeric_limits::min() > math::abs(value + std::numbers::pi_v)) { return -1; } + + // cos(x) = (1 - tan²(x/2)) / (1 + tan²(x/2)) + const auto z = math::tan(value / static_cast(2)); + return (static_cast(1) - z * z) / (static_cast(1) + z * z); + } + #endif + + return static_cast(std::cos(value)); + } +} + +#undef CMATH_WORKAROUND_REQUIRED diff --git a/src/math/math.hpp b/src/math/math.hpp new file mode 100644 index 00000000..5452aed3 --- /dev/null +++ b/src/math/math.hpp @@ -0,0 +1,8 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include diff --git a/src/memory/memory.hpp b/src/memory/memory.hpp new file mode 100644 index 00000000..def141f0 --- /dev/null +++ b/src/memory/memory.hpp @@ -0,0 +1,8 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include diff --git a/src/memory/memory.ixx b/src/memory/memory.ixx deleted file mode 100644 index 97509226..00000000 --- a/src/memory/memory.ixx +++ /dev/null @@ -1,16 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -export module gal.prometheus.memory; - -export import :read_write; - -#else -#pragma once - -#include - -#endif diff --git a/src/memory/read_write.ixx b/src/memory/rw.hpp similarity index 74% rename from src/memory/read_write.ixx rename to src/memory/rw.hpp index 255a2745..c2404afa 100644 --- a/src/memory/read_write.ixx +++ b/src/memory/rw.hpp @@ -1,39 +1,30 @@ // This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal +// Copyright (C) 2022-2025 Life4gal // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.memory:read_write; - -import std; -import gal.prometheus.functional; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -#else #pragma once #include -#include #include +#include #include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE -#endif +#include -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::memory) +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace gal::prometheus::memory { template requires std::is_arithmetic_v and ( functional::type_list< - char, const char, signed char, const signed char, unsigned char, const unsigned char, std::byte, const std::byte // + char, const char, + signed char, const signed char, + unsigned char, const unsigned char, + std::byte, const std::byte // >.any() ) [[nodiscard]] constexpr auto unaligned_load(const In* source) noexcept -> T @@ -70,13 +61,19 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::memory) template requires std::is_arithmetic_v - [[nodiscard]] constexpr auto unaligned_load(const void* source) noexcept -> T { return unaligned_load(static_cast(source)); } + [[nodiscard]] constexpr auto unaligned_load(const void* source) noexcept -> T + { + return memory::unaligned_load(static_cast(source)); + } template requires std::is_arithmetic_v and ( functional::type_list< - char, const char, signed char, const signed char, unsigned char, const unsigned char, std::byte, const std::byte // + char, const char, + signed char, const signed char, + unsigned char, const unsigned char, + std::byte, const std::byte // >.any() ) constexpr auto unaligned_store(const T value, Out* dest) noexcept -> void @@ -113,5 +110,8 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::memory) template requires std::is_arithmetic_v - constexpr auto unaligned_store(const T value, void* dest) noexcept -> void { unaligned_store(value, static_cast(dest)); } + constexpr auto unaligned_store(const T value, void* dest) noexcept -> void + { + memory::unaligned_store(value, static_cast(dest)); + } } diff --git a/src/meta/dimension.cache.inl b/src/meta/dimension.cache.inl new file mode 100644 index 00000000..bafcd154 --- /dev/null +++ b/src/meta/dimension.cache.inl @@ -0,0 +1,1079 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +namespace gal::prometheus::meta::dimension_detail +{ + // =========================================================================== + // CODE GEN MACRO + // =========================================================================== + + #if defined(GAL_PROMETHEUS_COMPILER_GNU) + // return requires + // { + // { + // meta::member_of_index(std::declval()) > + // std::declval() + // } -> std::convertible_to; + // }; + // + // error: expected ‘}’ before ‘>’ token + // meta::member_of_index(std::declval()) > + // note: to match this ‘{’ + // { + // ^ + // error: expected ‘;’ before ‘}’ token + // std::declval() + // ^ + // ; + // } -> std::convertible_to; + // ~ + // error: wrong number of template arguments (1, should be 2) + // } -> std::convertible_to; + // ^~~~~~~~~~~~~~~~~~~~ + + // before: requires { L > R } -> std::convertible_to; + // after: requires { (L > R) } -> std::convertible_to; + #endif + + #define DIMENSION_CACHE_GEN_FUNCTION_DIMENSION(operation, op) \ + constexpr auto f = []() noexcept -> bool\ + {\ + if constexpr (\ + requires\ + {\ + {(\ + meta::member_of_index(std::declval()) op\ + meta::member_of_index(std::declval())\ + )} -> std::convertible_to;\ + }\ + )\ + {\ + return true;\ + }\ + else \ + {\ + if constexpr ([[maybe_unused]] constexpr auto value = dimension_folder::value;\ + value == DimensionFoldCategory::ANY)\ + {\ + return requires\ + {\ + {\ + std::ranges::any_of(\ + meta::member_of_index(std::declval()) op\ + meta::member_of_index(std::declval()),\ + std::identity{}\ + )\ + } -> std::convertible_to;\ + };\ + }\ + else if constexpr (value == DimensionFoldCategory::ALL)\ + {\ + return requires\ + {\ + {\ + std::ranges::all_of(\ + meta::member_of_index(std::declval()) op\ + meta::member_of_index(std::declval()),\ + std::identity{}\ + )\ + } -> std::convertible_to;\ + };\ + }\ + else\ + {\ + return false;\ + }\ + }\ + } + + #define DIMENSION_CACHE_GEN_FUNCTION_VALUE(operation, op) \ + constexpr auto f = []() noexcept -> bool\ + {\ + if constexpr (\ + requires\ + {\ + {(\ + meta::member_of_index(std::declval()) op\ + std::declval()\ + )} -> std::convertible_to;\ + }\ + )\ + {\ + return true;\ + }\ + else \ + {\ + if constexpr ([[maybe_unused]] constexpr auto value = dimension_folder::value;\ + value == DimensionFoldCategory::ANY)\ + {\ + return requires\ + {\ + {\ + std::ranges::any_of(\ + meta::member_of_index(std::declval()) op\ + std::declval(),\ + std::identity{}\ + )\ + } -> std::convertible_to;\ + };\ + }\ + else if constexpr (value == DimensionFoldCategory::ALL)\ + {\ + return requires\ + {\ + {\ + std::ranges::all_of(\ + meta::member_of_index(std::declval()) op\ + std::declval(),\ + std::identity{}\ + )\ + } -> std::convertible_to;\ + };\ + }\ + else\ + {\ + return false;\ + }\ + }\ + } + + #define DIMENSION_CACHE_GEN_FUNCTION_SELF(operation, op) \ + constexpr auto f = []() noexcept -> bool\ + {\ + if constexpr (\ + requires\ + {\ + {\ + op meta::member_of_index(std::declval())\ + } -> std::convertible_to;\ + }\ + )\ + {\ + return true;\ + }\ + else \ + {\ + if constexpr ([[maybe_unused]] constexpr auto value = dimension_folder::value;\ + value == DimensionFoldCategory::ANY)\ + {\ + return requires\ + {\ + {\ + std::ranges::any_of(\ + op meta::member_of_index(std::declval()),\ + std::identity{}\ + )\ + } -> std::convertible_to;\ + };\ + }\ + else if constexpr (value == DimensionFoldCategory::ALL)\ + {\ + return requires\ + {\ + {\ + std::ranges::all_of(\ + op meta::member_of_index(std::declval()),\ + std::identity{}\ + )\ + } -> std::convertible_to;\ + };\ + }\ + else\ + {\ + return false;\ + }\ + }\ + } + + // =========================================================================== + // operator+= / operator+ + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs = lhs + rhs + return requires + { + meta::member_of_index(std::declval()) = + meta::member_of_index(std::declval()) + + meta::member_of_index(std::declval()); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs = lhs + rhs + return requires + { + meta::member_of_index(std::declval()) = + meta::member_of_index(std::declval()) + + std::declval(); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs += rhs + return requires + { + meta::member_of_index(std::declval()) += + meta::member_of_index(std::declval()); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs += rhs + return requires + { + meta::member_of_index(std::declval()) += + std::declval(); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + // =========================================================================== + // operator-= / operator- + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs = lhs - rhs + return requires + { + meta::member_of_index(std::declval()) = + meta::member_of_index(std::declval()) - + meta::member_of_index(std::declval()); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs = lhs - rhs + return requires + { + meta::member_of_index(std::declval()) = + meta::member_of_index(std::declval()) - + std::declval(); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs -= rhs + return requires + { + meta::member_of_index(std::declval()) -= + meta::member_of_index(std::declval()); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs -= rhs + return requires + { + meta::member_of_index(std::declval()) -= + std::declval(); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + // =========================================================================== + // operator*= / operator* + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs = lhs * rhs + return requires + { + meta::member_of_index(std::declval()) = + meta::member_of_index(std::declval()) * + meta::member_of_index(std::declval()); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs = lhs * rhs + return requires + { + meta::member_of_index(std::declval()) = + meta::member_of_index(std::declval()) * + std::declval(); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs = lhs *= rhs + return requires + { + meta::member_of_index(std::declval()) *= + meta::member_of_index(std::declval()); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs *= rhs + return requires + { + meta::member_of_index(std::declval()) *= + std::declval(); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + // =========================================================================== + // operator/= / operator/ + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs = lhs / rhs + return requires + { + meta::member_of_index(std::declval()) = + meta::member_of_index(std::declval()) / + meta::member_of_index(std::declval()); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs = lhs / rhs + return requires + { + meta::member_of_index(std::declval()) = + meta::member_of_index(std::declval()) / + std::declval(); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs /= rhs + return requires + { + meta::member_of_index(std::declval()) /= + meta::member_of_index(std::declval()); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs /= rhs + return requires + { + meta::member_of_index(std::declval()) /= + std::declval(); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + // =========================================================================== + // operator%= / operator% + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs = lhs % rhs + return requires + { + meta::member_of_index(std::declval()) = + meta::member_of_index(std::declval()) % + meta::member_of_index(std::declval()); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs = lhs % rhs + return requires + { + meta::member_of_index(std::declval()) = + meta::member_of_index(std::declval()) % + std::declval(); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs %= rhs + return requires + { + meta::member_of_index(std::declval()) %= + meta::member_of_index(std::declval()); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs %= rhs + return requires + { + meta::member_of_index(std::declval()) %= + std::declval(); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + // =========================================================================== + // operator&= / operator& + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs = lhs & rhs + return requires + { + meta::member_of_index(std::declval()) = + meta::member_of_index(std::declval()) & + meta::member_of_index(std::declval()); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs = lhs & rhs + return requires + { + meta::member_of_index(std::declval()) = + meta::member_of_index(std::declval()) & + std::declval(); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs &= rhs + return requires + { + meta::member_of_index(std::declval()) &= + meta::member_of_index(std::declval()); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs &= rhs + return requires + { + meta::member_of_index(std::declval()) &= + std::declval(); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + // =========================================================================== + // operator|= / operator| + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs = lhs | rhs + return requires + { + meta::member_of_index(std::declval()) = + meta::member_of_index(std::declval()) | + meta::member_of_index(std::declval()); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs = lhs | rhs + return requires + { + meta::member_of_index(std::declval()) = + meta::member_of_index(std::declval()) | + std::declval(); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs |= rhs + return requires + { + meta::member_of_index(std::declval()) |= + meta::member_of_index(std::declval()); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs |= rhs + return requires + { + meta::member_of_index(std::declval()) |= + std::declval(); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + // =========================================================================== + // operator^= / operator^ + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs = lhs ^ rhs + return requires + { + meta::member_of_index(std::declval()) = + meta::member_of_index(std::declval()) ^ + meta::member_of_index(std::declval()); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs = lhs ^ rhs + return requires + { + meta::member_of_index(std::declval()) = + meta::member_of_index(std::declval()) ^ + std::declval(); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs ^= rhs + return requires + { + meta::member_of_index(std::declval()) ^= + meta::member_of_index(std::declval()); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // lhs ^= rhs + return requires + { + meta::member_of_index(std::declval()) ^= + std::declval(); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + // =========================================================================== + // operator~= / operator~ + + template + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + constexpr auto f = []() noexcept -> bool + { + // ~self + return requires + { + meta::member_of_index(std::declval()) = + ~meta::member_of_index(std::declval()); + }; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + // =========================================================================== + // operator and + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + DIMENSION_CACHE_GEN_FUNCTION_DIMENSION(DimensionFoldOperation::LOGICAL_AND, and); + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + DIMENSION_CACHE_GEN_FUNCTION_VALUE(DimensionFoldOperation::LOGICAL_AND, and); + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + // =========================================================================== + // operator or + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + DIMENSION_CACHE_GEN_FUNCTION_DIMENSION(DimensionFoldOperation::LOGICAL_OR, or); + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + DIMENSION_CACHE_GEN_FUNCTION_VALUE(DimensionFoldOperation::LOGICAL_OR, or); + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + // =========================================================================== + // operator not + + template + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + DIMENSION_CACHE_GEN_FUNCTION_SELF(DimensionFoldOperation::LOGICAL_NOT, not); + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + // =========================================================================== + // operator== + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + DIMENSION_CACHE_GEN_FUNCTION_DIMENSION(DimensionFoldOperation::EQUAL, ==); + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + DIMENSION_CACHE_GEN_FUNCTION_VALUE(DimensionFoldOperation::EQUAL, ==); + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + // =========================================================================== + // operator!= + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + DIMENSION_CACHE_GEN_FUNCTION_DIMENSION(DimensionFoldOperation::NOT_EQUAL, !=); + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + DIMENSION_CACHE_GEN_FUNCTION_VALUE(DimensionFoldOperation::NOT_EQUAL, !=); + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + // =========================================================================== + // operator> + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + DIMENSION_CACHE_GEN_FUNCTION_DIMENSION(DimensionFoldOperation::GREATER_THAN, >); + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + DIMENSION_CACHE_GEN_FUNCTION_VALUE(DimensionFoldOperation::GREATER_THAN, >); + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + // =========================================================================== + // operator>= + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + DIMENSION_CACHE_GEN_FUNCTION_DIMENSION(DimensionFoldOperation::GREATER_EQUAL, >=); + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + DIMENSION_CACHE_GEN_FUNCTION_VALUE(DimensionFoldOperation::GREATER_EQUAL, >=); + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + // =========================================================================== + // operator< + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + DIMENSION_CACHE_GEN_FUNCTION_DIMENSION(DimensionFoldOperation::LESS_THAN, <); + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + DIMENSION_CACHE_GEN_FUNCTION_VALUE(DimensionFoldOperation::LESS_THAN, <); + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + // =========================================================================== + // operator<= + + template OtherDimension> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + DIMENSION_CACHE_GEN_FUNCTION_DIMENSION(DimensionFoldOperation::LESS_EQUAL, <=); + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + template T> + struct cache : std::bool_constant< + // manually member_walk + [](std::index_sequence) consteval noexcept -> bool + { + DIMENSION_CACHE_GEN_FUNCTION_VALUE(DimensionFoldOperation::LESS_EQUAL, <=); + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}) + > {}; + + #undef DIMENSION_CACHE_GEN_FUNCTION_DIMENSION + #undef DIMENSION_CACHE_GEN_FUNCTION_VALUE + #undef DIMENSION_CACHE_GEN_FUNCTION_SELF +} diff --git a/src/meta/dimension.hpp b/src/meta/dimension.hpp new file mode 100644 index 00000000..e4f0902b --- /dev/null +++ b/src/meta/dimension.hpp @@ -0,0 +1,2185 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include + +namespace gal::prometheus::meta +{ + enum class Dimensions : std::size_t + { + ALL = std::numeric_limits::max(), + }; + + template + struct dimension; + + enum class DimensionFoldCategory : std::uint8_t + { + NONE, + ANY, + ALL, + }; + + enum class DimensionFoldOperation : std::uint8_t + { + LOGICAL_AND, + LOGICAL_OR, + LOGICAL_NOT, + EQUAL, + NOT_EQUAL, + GREATER_THAN, + GREATER_EQUAL, + LESS_THAN, + LESS_EQUAL, + }; + + /** + * @brief Specify how the dimension should be folded + * @code + * template<> + * struct dimension_folder + * { + * constexpr static auto value = DimensionFoldCategory::ALL; + * }; + * + * MyDimension d1{...}; + * MyDimension d2{...}; + * const auto logical_or = d1 or d2; // boolean_result_type (aka std::array()>) + * const auto logical_and = d1 and d2; // bool + * @endcode + */ + template + struct dimension_folder + { + using dimension_type = Dimension; + constexpr static auto operation = Operation; + + constexpr static auto value = DimensionFoldCategory::NONE; + }; + + namespace dimension_detail + { + template + constexpr auto is_dimension_v = false; + + // template + // constexpr auto is_dimension_v> = true; + + template + requires std::is_base_of_v, Dimension> + constexpr auto is_dimension_v = true; + + template + concept dimension_t = is_dimension_v; + + // fixme: + // `dimension + T{value}` will preferentially try to match `dimension + dimension`, + // and then generate a compilation error (T in member_size does not satisfy the constraints of member_size) + template + concept known_member_size_t = requires { member_size(); }; + + template + [[nodiscard]] consteval auto is_compatible_dimension() noexcept -> bool + { + if constexpr (member_size() != member_size()) + { + return false; + } + else + { + return [](std::index_sequence) noexcept -> bool + { + const auto f = []() noexcept -> bool + { + using this_type = member_type_of_index; + using other_type = member_type_of_index; + + return + // implicit + std::is_convertible_v or + // explicit + std::is_constructible_v; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}); + } + } + + template + concept compatible_dimension_t = + known_member_size_t and + is_compatible_dimension(); + + template + [[nodiscard]] consteval auto is_compatible_dimension_like() noexcept -> bool + { + if constexpr (dimension_t) + { + return false; + } + else + { + return is_compatible_dimension(); + } + } + + template + concept compatible_dimension_like_t = + known_member_size_t and + is_compatible_dimension_like(); + + template + concept compatible_dimension_or_dimension_like_t = + compatible_dimension_t or + compatible_dimension_like_t; + + template + [[nodiscard]] consteval auto is_compatible_value_type() noexcept -> bool + { + return [](std::index_sequence) noexcept -> bool + { + const auto f = []() noexcept -> bool + { + using this_type = member_type_of_index; + + return + // implicit + std::is_convertible_v or + // explicit + std::is_constructible_v; + }; + + return (f.template operator()() and ...); + }(std::make_index_sequence()>{}); + } + + template + concept compatible_value_type_t = is_compatible_value_type(); + + template + using boolean_result_type = std::array()>; + + template + requires (std::is_same_v or std::is_same_v>) + [[nodiscard]] constexpr auto do_fold(const Result result) noexcept -> auto + { + if constexpr (constexpr auto value = dimension_folder::value; + value == DimensionFoldCategory::NONE) + { + return result; + } + else if constexpr (value == DimensionFoldCategory::ANY) + { + return std::ranges::any_of(result, std::identity{}); + } + else if constexpr (value == DimensionFoldCategory::ALL) + { + return std::ranges::all_of(result, std::identity{}); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + // Tag, ThisDimension, T + template + struct cache : std::false_type {}; + + struct tag_transform {}; + + struct tag_addition {}; + + struct tag_addition_self {}; + + struct tag_subtraction {}; + + struct tag_subtraction_self {}; + + struct tag_multiplication {}; + + struct tag_multiplication_self {}; + + struct tag_division {}; + + struct tag_division_self {}; + + struct tag_modulus {}; + + struct tag_modulus_self {}; + + struct tag_bit_and {}; + + struct tag_bit_and_self {}; + + struct tag_bit_or {}; + + struct tag_bit_or_self {}; + + struct tag_bit_xor {}; + + struct tag_bit_xor_self {}; + + struct tag_bit_flip {}; + + struct tag_logical_and {}; + + struct tag_logical_or {}; + + struct tag_logical_not {}; + + struct tag_compare_equal {}; + + struct tag_compare_not_equal {}; + + struct tag_compare_greater_than {}; + + struct tag_compare_greater_equal {}; + + struct tag_compare_less_than {}; + + struct tag_compare_less_equal {}; + + template + constexpr auto is_operation_supported_v = cache::value; + + template + concept operation_supported_t = is_operation_supported_v; + + // Disguises type T as an N-dimensional structure, but actually returns the same object in all dimensions + template + struct dimension_wrapper + { + std::reference_wrapper ref; + + template + [[nodiscard]] constexpr auto get() const noexcept -> T& + { + std::ignore = Index; + return ref.get(); + } + }; + + // Convert current dimension to target dimension/dimension_like/value_type, member types must be convertible (one-to-one) + // Generally used to convert a dimension to another compatible type + template + struct dimension_transformer + { + template + [[nodiscard]] constexpr auto operator()(const auto& self) const noexcept -> decltype(auto) + { + if constexpr (known_member_size_t) + { + return static_cast>(self); + } + else + { + std::ignore = Index; + return static_cast(self); + } + } + }; + + template + struct dimension_walker + { + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH + + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(4244) + #endif + + // lhs `op`= rhs + // lhs = `op` rhs + template + constexpr auto operator()(auto& lhs, const auto& rhs) const noexcept -> void + { + // If the index is the specified one, + // then the operation will be performed (lhs `op`= rhs or lhs = `op` rhs), + // otherwise, the binary operations can be handled uniformly (do nothing), + // but not unary operations, which still require assignment (unless they were assigned before walk). + + if constexpr (D != Dimensions::ALL and static_cast(D) != Index) + { + if constexpr ( + std::is_same_v or + std::is_same_v + ) + { + lhs = rhs; + } + } + else + { + std::ignore = Index; + if constexpr (std::is_same_v) + { + lhs += rhs; + } + else if constexpr (std::is_same_v) + { + lhs -= rhs; + } + else if constexpr (std::is_same_v) + { + lhs *= rhs; + } + else if constexpr (std::is_same_v) + { + lhs /= rhs; + } + else if constexpr (std::is_same_v) + { + lhs %= rhs; + } + else if constexpr (std::is_same_v) + { + lhs &= rhs; + } + else if constexpr (std::is_same_v) + { + lhs |= rhs; + } + else if constexpr (std::is_same_v) + { + lhs ^= rhs; + } + else if constexpr (std::is_same_v) + { + lhs = ~rhs; + } + else if constexpr (std::is_same_v) + { + lhs = not rhs; + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + } + + // result = lhs `op` rhs + template + constexpr auto operator()(auto& result, const auto& lhs, const auto& rhs) const noexcept -> void + { + // If the index is the specified one, + // then the operation will be performed (result = lhs `op` rhs or result = lhs(rhs)), + // otherwise, the value will be assigned directly (result = lhs). + + if constexpr (D != Dimensions::ALL and static_cast(D) != Index) + { + if constexpr (requires { result = lhs; }) + { + result = lhs; + } + else if constexpr (requires { result = static_cast>(lhs); }) + { + result = static_cast>(lhs); + } + else if constexpr (requires { result = std::remove_cvref_t{lhs}; }) + { + result = std::remove_cvref_t{lhs}; + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else + { + if constexpr (std::is_same_v) + { + if constexpr (requires { lhs(rhs); }) + { + result = lhs(rhs); + } + else if constexpr (requires { lhs.template operator()(rhs); }) + { + result = lhs.template operator()(rhs); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (std::is_same_v) + { + result = lhs + rhs; + } + else if constexpr (std::is_same_v) + { + result = lhs - rhs; + } + else if constexpr (std::is_same_v) + { + result = lhs * rhs; + } + else if constexpr (std::is_same_v) + { + result = lhs / rhs; + } + else if constexpr (std::is_same_v) + { + result = lhs % rhs; + } + else if constexpr (std::is_same_v) + { + result = lhs & rhs; + } + else if constexpr (std::is_same_v) + { + result = lhs | rhs; + } + else if constexpr (std::is_same_v) + { + result = lhs ^ rhs; + } + else if constexpr (std::is_same_v) + { + result = lhs and rhs; + } + else if constexpr (std::is_same_v) + { + result = lhs or rhs; + } + else if constexpr (std::is_same_v) + { + result = lhs == rhs; + } + else if constexpr (std::is_same_v) + { + result = lhs != rhs; + } + else if constexpr (std::is_same_v) + { + result = lhs > rhs; + } + else if constexpr (std::is_same_v) + { + result = lhs >= rhs; + } + else if constexpr (std::is_same_v) + { + result = lhs < rhs; + } + else if constexpr (std::is_same_v) + { + result = lhs <= rhs; + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + } + + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP + + template + constexpr auto walk(Lhs& lhs, const Rhs& rhs) const noexcept -> void + { + meta::member_walk(*this, lhs, rhs); + } + + template + constexpr auto walk(Result& result, const Lhs& lhs, const Rhs& rhs) const noexcept -> void + { + meta::member_walk(*this, result, lhs, rhs); + } + }; + } + + template + struct [[nodiscard]] dimension // NOLINT(bugprone-crtp-constructor-accessibility) + { + using dimension_type = Dimension; + + private: + [[nodiscard]] constexpr auto rep() noexcept -> dimension_type& { return *static_cast(this); } + [[nodiscard]] constexpr auto rep() const noexcept -> const dimension_type& { return *static_cast(this); } + + template + constexpr static auto walk(auto& lhs, const auto& rhs) noexcept -> void + { + dimension_detail::dimension_walker{}.walk(lhs, rhs); + } + + template + constexpr static auto walk(auto& result, const auto& lhs, const auto& rhs) noexcept -> void + { + dimension_detail::dimension_walker{}.walk(result, lhs, rhs); + } + + public: + // =========================================================================== + // from + + template< + dimension_detail::compatible_dimension_or_dimension_like_t FromDimension + > + [[nodiscard]] constexpr static auto from(const FromDimension& from) noexcept -> dimension_type + { + const auto transformer = dimension_detail::dimension_transformer{}; + const auto wrapper = dimension_detail::dimension_wrapper, meta::member_size()>{.ref = transformer}; + + dimension_type result{}; + dimension::walk(result, wrapper, from); + + return result; + } + + template< + dimension_detail::compatible_value_type_t T + > + [[nodiscard]] constexpr static auto from(const T& value) noexcept -> dimension_type + { + const auto transformer = dimension_detail::dimension_transformer{}; + const auto wrapper = dimension_detail::dimension_wrapper, meta::member_size()>{.ref = transformer}; + const auto value_wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension_type result{}; + dimension::walk(result, wrapper, value_wrapper); + + return result; + } + + // =========================================================================== + // to + + template< + dimension_detail::compatible_dimension_or_dimension_like_t TargetDimension = dimension_type, + typename TransformFunction = dimension_detail::dimension_transformer> + [[nodiscard]] constexpr auto to(const TransformFunction& transform_function = {}) const noexcept -> TargetDimension + { + const dimension_detail::dimension_wrapper()> wrapper{.ref = transform_function}; + + TargetDimension result{}; + dimension::walk(result, wrapper, rep()); + + return result; + } + + [[nodiscard]] constexpr auto copy() const noexcept -> dimension_type + { + return to(); + } + + // =========================================================================== + // all / any / none + + [[nodiscard]] constexpr auto all() const noexcept -> bool + { + const auto result = to>(); + return std::ranges::all_of(result, std::identity{}); + } + + [[nodiscard]] constexpr auto any() const noexcept -> bool + { + const auto result = to>(); + return std::ranges::any_of(result, std::identity{}); + } + + [[nodiscard]] constexpr auto none() const noexcept -> bool + { + const auto result = to>(); + return std::ranges::none_of(result, std::identity{}); + } + + // =========================================================================== + // operator+= / operator+ + + // dimension += dimension / dimension_like + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + constexpr auto add_equal(const OtherDimension& other) noexcept -> dimension_type& + { + dimension::walk(rep(), other); + + return rep(); + } + + // dimension + dimension / dimension_like + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto add(const OtherDimension& other) const noexcept -> dimension_type + { + dimension_type result{}; + dimension::walk(result, rep(), other); + + return result; + } + + // dimension += value + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_value_type_t T + > + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + constexpr auto add_equal(const T& value) noexcept -> dimension_type& + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension::walk(rep(), wrapper); + + return rep(); + } + + // dimension + value + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_value_type_t T + > + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto add(const T& value) const noexcept -> dimension_type + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension_type result{}; + dimension::walk(result, rep(), wrapper); + + return result; + } + + // =========================================================================== + // operator-= / operator- + + // dimension -= dimension / dimension_like + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + constexpr auto subtract_equal(const OtherDimension& other) noexcept -> dimension_type& + { + dimension::walk(rep(), other); + + return rep(); + } + + // dimension - dimension / dimension_like + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto subtract(const OtherDimension& other) const noexcept -> dimension_type + { + dimension_type result{}; + dimension::walk(result, rep(), other); + + return result; + } + + // dimension -= value + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_value_type_t T + > + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + constexpr auto subtract_equal(const T& value) noexcept -> dimension_type& + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension::walk(rep(), wrapper); + + return rep(); + } + + // dimension - value + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_value_type_t T + > + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto subtract(const T& value) const noexcept -> dimension_type + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension_type result{}; + dimension::walk(result, rep(), wrapper); + + return result; + } + + // =========================================================================== + // operator*= / operator* + + // dimension *= dimension / dimension_like + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + constexpr auto multiply_equal(const OtherDimension& other) noexcept -> dimension_type& + { + dimension::walk(rep(), other); + + return rep(); + } + + // dimension * dimension / dimension_like + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto multiply(const OtherDimension& other) const noexcept -> dimension_type + { + dimension_type result{}; + dimension::walk(result, rep(), other); + + return result; + } + + // dimension *= value + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_value_type_t T + > + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + constexpr auto multiply_equal(const T& value) noexcept -> dimension_type& + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension::walk(rep(), wrapper); + + return rep(); + } + + // dimension * value + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_value_type_t T + > + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto multiply(const T& value) const noexcept -> dimension_type + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension_type result{}; + dimension::walk(result, rep(), wrapper); + + return result; + } + + // =========================================================================== + // operator/= / operator/ + + // dimension /= dimension / dimension_like + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + constexpr auto divide_equal(const OtherDimension& other) noexcept -> dimension_type& + { + dimension::walk(rep(), other); + + return rep(); + } + + // dimension / dimension / dimension_like + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto divide(const OtherDimension& other) const noexcept -> dimension_type + { + dimension_type result{}; + dimension::walk(result, rep(), other); + + return result; + } + + // dimension /= value + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_value_type_t T + > + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + constexpr auto divide_equal(const T& value) noexcept -> dimension_type& + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension::walk(rep(), wrapper); + + return rep(); + } + + // dimension / value + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_value_type_t T + > + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto divide(const T& value) const noexcept -> dimension_type + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension_type result{}; + dimension::walk(result, rep(), wrapper); + + return result; + } + + // =========================================================================== + // operator%= / operator% + + // dimension %= dimension / dimension_like + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + constexpr auto mod_equal(const OtherDimension& other) noexcept -> dimension_type& + { + dimension::walk(rep(), other); + + return rep(); + } + + // dimension % dimension / dimension_like + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto mod(const OtherDimension& other) const noexcept -> dimension_type + { + dimension_type result{}; + dimension::walk(result, rep(), other); + + return result; + } + + // dimension %= value + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_value_type_t T + > + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + constexpr auto mod_equal(const T& value) noexcept -> dimension_type& + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension::walk(rep(), wrapper); + + return rep(); + } + + // dimension % value + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_value_type_t T + > + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto mod(const T& value) const noexcept -> dimension_type + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension_type result{}; + dimension::walk(result, rep(), wrapper); + + return result; + } + + // =========================================================================== + // operator&= / operator& + + // dimension &= dimension / dimension_like + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + constexpr auto bit_and_equal(const OtherDimension& other) noexcept -> dimension_type& + { + dimension::walk(rep(), other); + + return rep(); + } + + // dimension & dimension / dimension_like + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto bit_and(const OtherDimension& other) const noexcept -> dimension_type + { + dimension_type result{}; + dimension::walk(result, rep(), other); + + return result; + } + + // dimension &= value + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_value_type_t T + > + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + constexpr auto bit_and_equal(const T& value) noexcept -> dimension_type& + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension::walk(rep(), wrapper); + + return rep(); + } + + // dimension & value + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_value_type_t T + > + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto bit_and(const T& value) const noexcept -> dimension_type + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension_type result{}; + dimension::walk(result, rep(), wrapper); + + return result; + } + + // =========================================================================== + // operator|= / operator| + + // dimension |= dimension / dimension_like + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + constexpr auto bit_or_equal(const OtherDimension& other) noexcept -> dimension_type& + { + dimension::walk(rep(), other); + + return rep(); + } + + // dimension | dimension / dimension_like + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto bit_or(const OtherDimension& other) const noexcept -> dimension_type + { + dimension_type result{}; + dimension::walk(result, rep(), other); + + return result; + } + + // dimension |= value + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_value_type_t T + > + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + constexpr auto bit_or_equal(const T& value) noexcept -> dimension_type& + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension::walk(rep(), wrapper); + + return rep(); + } + + // dimension | value + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_value_type_t T + > + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto bit_or(const T& value) const noexcept -> dimension_type + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension_type result{}; + dimension::walk(result, rep(), wrapper); + + return result; + } + + // =========================================================================== + // operator^= / operator^ + + // dimension ^= dimension / dimension_like + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + constexpr auto bit_xor_equal(const OtherDimension& other) noexcept -> dimension_type& + { + dimension::walk(rep(), other); + + return rep(); + } + + // dimension ^ dimension / dimension_like + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto bit_xor(const OtherDimension& other) const noexcept -> dimension_type + { + dimension_type result{}; + dimension::walk(result, rep(), other); + + return result; + } + + // dimension ^= value + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_value_type_t T + > + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + constexpr auto bit_xor_equal(const T& value) noexcept -> dimension_type& + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension::walk(rep(), wrapper); + + return rep(); + } + + // dimension ^ value + template< + Dimensions D = Dimensions::ALL, + dimension_detail::compatible_value_type_t T + > + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto bit_xor(const T& value) const noexcept -> dimension_type + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension_type result{}; + dimension::walk(result, rep(), wrapper); + + return result; + } + + // =========================================================================== + // operator~ + + // ~dimension + template< + Dimensions D = Dimensions::ALL + > + requires( + (D == Dimensions::ALL or static_cast(D) < meta::member_size()) and + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto bit_flip() const noexcept -> dimension_type + { + dimension_type result{}; + dimension::walk(result, rep()); + + return result; + } + + + // =========================================================================== + // operator and + + // dimension and dimension / dimension_like + template< + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto logical_and(const OtherDimension& other) const noexcept -> auto // dimension_detail::boolean_result_type + { + dimension_detail::boolean_result_type result{}; + dimension::walk(result, rep(), other); + + return dimension_detail::do_fold(result); + } + + // dimension and value + template< + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto logical_and(const T& value) const noexcept -> auto // dimension_detail::boolean_result_type + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension_detail::boolean_result_type result{}; + dimension::walk(result, rep(), wrapper); + + return dimension_detail::do_fold(result); + } + + // =========================================================================== + // operator or + + // dimension or dimension / dimension_like + template< + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto logical_or(const OtherDimension& other) const noexcept -> auto // dimension_detail::boolean_result_type + { + dimension_detail::boolean_result_type result{}; + dimension::walk(result, rep(), other); + + return dimension_detail::do_fold(result); + } + + // dimension or value + template< + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto logical_or(const T& value) const noexcept -> auto // dimension_detail::boolean_result_type + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension_detail::boolean_result_type result{}; + dimension::walk(result, rep(), wrapper); + + return dimension_detail::do_fold(result); + } + + // =========================================================================== + // operator not + + // not dimension + [[nodiscard]] constexpr auto logical_not() const noexcept -> auto // dimension_detail::boolean_result_type + requires ( + dimension_detail::operation_supported_t + ) + { + dimension_detail::boolean_result_type result{}; + dimension::walk(result, rep()); + + return dimension_detail::do_fold(result); + } + + // =========================================================================== + // operator== + + // dimension == dimension / dimension_like + template< + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto equal(const OtherDimension& other) const noexcept -> auto // dimension_detail::boolean_result_type + { + dimension_detail::boolean_result_type result{}; + dimension::walk(result, rep(), other); + + return dimension_detail::do_fold(result); + } + + // dimension == value + template< + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto equal(const T& value) const noexcept -> auto // dimension_detail::boolean_result_type + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension_detail::boolean_result_type result{}; + dimension::walk(result, rep(), wrapper); + + return dimension_detail::do_fold(result); + } + + // =========================================================================== + // operator!= + + // dimension != dimension / dimension_like + template< + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto not_equal(const OtherDimension& other) const noexcept -> auto // dimension_detail::boolean_result_type + { + dimension_detail::boolean_result_type result{}; + dimension::walk(result, rep(), other); + + return dimension_detail::do_fold(result); + } + + // dimension != value + template< + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto not_equal(const T& value) const noexcept -> auto // dimension_detail::boolean_result_type + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension_detail::boolean_result_type result{}; + dimension::walk(result, rep(), wrapper); + + return dimension_detail::do_fold(result); + } + + // =========================================================================== + // operator> + + // dimension > dimension / dimension_like + template< + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto greater_than(const OtherDimension& other) const noexcept -> auto // dimension_detail::boolean_result_type + { + dimension_detail::boolean_result_type result{}; + dimension::walk(result, rep(), other); + + return dimension_detail::do_fold(result); + } + + // dimension > value + template< + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto greater_than(const T& value) const noexcept -> auto // dimension_detail::boolean_result_type + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension_detail::boolean_result_type result{}; + dimension::walk(result, rep(), wrapper); + + return dimension_detail::do_fold(result); + } + + // =========================================================================== + // operator>= + + // dimension >= dimension / dimension_like + template< + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto greater_equal(const OtherDimension& other) const noexcept -> auto // dimension_detail::boolean_result_type + { + dimension_detail::boolean_result_type result{}; + dimension::walk(result, rep(), other); + + return dimension_detail::do_fold(result); + } + + // dimension >= value + template< + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto greater_equal(const T& value) const noexcept -> auto // dimension_detail::boolean_result_type + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension_detail::boolean_result_type result{}; + dimension::walk(result, rep(), wrapper); + + return dimension_detail::do_fold(result); + } + + // =========================================================================== + // operator< + + // dimension < dimension / dimension_like + template< + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto less_than(const OtherDimension& other) const noexcept -> auto // dimension_detail::boolean_result_type + { + dimension_detail::boolean_result_type result{}; + dimension::walk(result, rep(), other); + + return dimension_detail::do_fold(result); + } + + // dimension < value + template< + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto less_than(const T& value) const noexcept -> auto // dimension_detail::boolean_result_type + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension_detail::boolean_result_type result{}; + dimension::walk(result, rep(), wrapper); + + return dimension_detail::do_fold(result); + } + + // =========================================================================== + // operator<= + + // dimension <= dimension / dimension_like + template< + dimension_detail::compatible_dimension_or_dimension_like_t OtherDimension = dimension_type> + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto less_equal(const OtherDimension& other) const noexcept -> auto // dimension_detail::boolean_result_type + { + dimension_detail::boolean_result_type result{}; + dimension::walk(result, rep(), other); + + return dimension_detail::do_fold(result); + } + + // dimension <= value + template< + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto less_equal(const T& value) const noexcept -> auto // dimension_detail::boolean_result_type + { + const auto wrapper = dimension_detail::dimension_wrapper()>{.ref = value}; + + dimension_detail::boolean_result_type result{}; + dimension::walk(result, rep(), wrapper); + + return dimension_detail::do_fold(result); + } + }; + + // =========================================================================== + // operator+= / operator+ + + // dimension += dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + constexpr auto operator+=(LeftDimension& lhs, const RightDimension& rhs) noexcept -> LeftDimension& + { + return lhs.add_equal(rhs); + } + + // dimension + dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator+(const LeftDimension& lhs, const RightDimension& rhs) noexcept -> LeftDimension + { + return lhs.add(rhs); + } + + // dimension += value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + constexpr auto operator+=(LeftDimension& lhs, const T& value) noexcept -> LeftDimension& + { + return lhs.add_equal(value); + } + + // dimension + value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator+(const LeftDimension& lhs, const T& value) noexcept -> LeftDimension + { + return lhs.add(value); + } + + // =========================================================================== + // operator-= / operator- + + // dimension -= dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + constexpr auto operator-=(LeftDimension& lhs, const RightDimension& rhs) noexcept -> LeftDimension& + { + return lhs.subtract_equal(rhs); + } + + // dimension - dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator-(const LeftDimension& lhs, const RightDimension& rhs) noexcept -> LeftDimension + { + return lhs.subtract(rhs); + } + + // dimension -= value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + constexpr auto operator-=(LeftDimension& lhs, const T& value) noexcept -> LeftDimension& + { + return lhs.subtract_equal(value); + } + + // dimension - value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator-(const LeftDimension& lhs, const T& value) noexcept -> LeftDimension + { + return lhs.subtract(value); + } + + // =========================================================================== + // operator*= / operator* + + // dimension *= dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + constexpr auto operator*=(LeftDimension& lhs, const RightDimension& rhs) noexcept -> LeftDimension& + { + return lhs.multiply_equal(rhs); + } + + // dimension * dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator*(const LeftDimension& lhs, const RightDimension& rhs) noexcept -> LeftDimension + { + return lhs.multiply(rhs); + } + + // dimension *= value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + constexpr auto operator*=(LeftDimension& lhs, const T& value) noexcept -> LeftDimension& + { + return lhs.multiply_equal(value); + } + + // dimension * value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator*(const LeftDimension& lhs, const T& value) noexcept -> LeftDimension + { + return lhs.multiply(value); + } + + // =========================================================================== + // operator/= / operator/ + + // dimension /= dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + constexpr auto operator/=(LeftDimension& lhs, const RightDimension& rhs) noexcept -> LeftDimension& + { + return lhs.divide_equal(rhs); + } + + // dimension / dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator/(const LeftDimension& lhs, const RightDimension& rhs) noexcept -> LeftDimension + { + return lhs.divide(rhs); + } + + // dimension /= value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + constexpr auto operator/=(LeftDimension& lhs, const T& value) noexcept -> LeftDimension& + { + return lhs.divide_equal(value); + } + + // dimension / value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator/(const LeftDimension& lhs, const T& value) noexcept -> LeftDimension + { + return lhs.divide(value); + } + + // =========================================================================== + // operator%= / operator% + + // dimension %= dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + constexpr auto operator%=(LeftDimension& lhs, const RightDimension& rhs) noexcept -> LeftDimension& + { + return lhs.mod_equal(rhs); + } + + // dimension % dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator%(const LeftDimension& lhs, const RightDimension& rhs) noexcept -> LeftDimension + { + return lhs.mod(rhs); + } + + // dimension %= value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + constexpr auto operator%=(LeftDimension& lhs, const T& value) noexcept -> LeftDimension& + { + return lhs.mod_equal(value); + } + + // dimension % value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator%(const LeftDimension& lhs, const T& value) noexcept -> LeftDimension + { + return lhs.mod(value); + } + + // =========================================================================== + // operator&= / operator& + + // dimension &= dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + constexpr auto operator&=(LeftDimension& lhs, const RightDimension& rhs) noexcept -> LeftDimension& + { + return lhs.bit_and_equal(rhs); + } + + // dimension & dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator&(const LeftDimension& lhs, const RightDimension& rhs) noexcept -> LeftDimension + { + return lhs.bit_and(rhs); + } + + // dimension &= value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + constexpr auto operator&=(LeftDimension& lhs, const T& value) noexcept -> LeftDimension& + { + return lhs.bit_and_equal(value); + } + + // dimension & value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator&(const LeftDimension& lhs, const T& value) noexcept -> LeftDimension + { + return lhs.bit_and(value); + } + + // =========================================================================== + // operator|= / operator| + + // dimension |= dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + constexpr auto operator|=(LeftDimension& lhs, const RightDimension& rhs) noexcept -> LeftDimension& + { + return lhs.bit_or_equal(rhs); + } + + // dimension | dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator|(const LeftDimension& lhs, const RightDimension& rhs) noexcept -> LeftDimension + { + return lhs.bit_or(rhs); + } + + // dimension |= value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + constexpr auto operator|=(LeftDimension& lhs, const T& value) noexcept -> LeftDimension& + { + return lhs.bit_or_equal(value); + } + + // dimension | value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator|(const LeftDimension& lhs, const T& value) noexcept -> LeftDimension + { + return lhs.bit_or(value); + } + + // =========================================================================== + // operator^= / operator| + + // dimension ^= dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + constexpr auto operator^=(LeftDimension& lhs, const RightDimension& rhs) noexcept -> LeftDimension& + { + return lhs.bit_xor_equal(rhs); + } + + // dimension ^ dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator^(const LeftDimension& lhs, const RightDimension& rhs) noexcept -> LeftDimension + { + return lhs.bit_xor(rhs); + } + + // dimension ^= value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + constexpr auto operator^=(LeftDimension& lhs, const T& value) noexcept -> LeftDimension& + { + return lhs.bit_xor_equal(value); + } + + // dimension ^ value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator^(const LeftDimension& lhs, const T& value) noexcept -> LeftDimension + { + return lhs.bit_xor(value); + } + + // =========================================================================== + // operator~ + + // ~dimension + template< + dimension_detail::dimension_t LeftDimension + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator~(const LeftDimension& lhs) noexcept -> LeftDimension + { + return lhs.bit_flip(); + } + + // =========================================================================== + // operator and + + // dimension and dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator and(const LeftDimension& lhs, const RightDimension& rhs) noexcept -> auto + { + return lhs.logical_and(rhs); + } + + // dimension and value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + constexpr auto operator and(const LeftDimension& lhs, const T& value) noexcept -> auto + { + return lhs.logical_and(value); + } + + // =========================================================================== + // operator or + + // dimension or dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator or(const LeftDimension& lhs, const RightDimension& rhs) noexcept -> auto + { + return lhs.logical_or(rhs); + } + + // dimension or value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + constexpr auto operator or(const LeftDimension& lhs, const T& value) noexcept -> auto + { + return lhs.logical_or(value); + } + + // =========================================================================== + // operator not + + // not dimension + template< + dimension_detail::dimension_t LeftDimension + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator not(const LeftDimension& lhs) noexcept -> auto + { + return lhs.logical_not(); + } + + // =========================================================================== + // operator== + + // dimension == dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator==(const LeftDimension& lhs, const RightDimension& rhs) noexcept -> auto + { + return lhs.equal(rhs); + } + + // dimension == value + template< + dimension_detail::dimension_t LeftDimension, + dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator==(const LeftDimension& lhs, const T& rhs) noexcept -> auto + { + return lhs.equal(rhs); + } + + // =========================================================================== + // operator!= + + // dimension != dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator!=(const LeftDimension& lhs, const RightDimension& rhs) noexcept -> auto + { + return lhs.not_equal(rhs); + } + + // dimension != value + template< + dimension_detail::dimension_t LeftDimension, dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator!=(const LeftDimension& lhs, const T& rhs) noexcept -> auto + { + return lhs.not_equal(rhs); + } + + // =========================================================================== + // operator> + + // dimension > dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator>(const LeftDimension& lhs, const RightDimension& rhs) noexcept -> auto + { + return lhs.greater_than(rhs); + } + + // dimension > value + template< + dimension_detail::dimension_t LeftDimension, dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator>(const LeftDimension& lhs, const T& rhs) noexcept -> auto + { + return lhs.greater_than(rhs); + } + + // =========================================================================== + // operator>= + + // dimension >= dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator>=(const LeftDimension& lhs, const RightDimension& rhs) noexcept -> auto + { + return lhs.greater_equal(rhs); + } + + // dimension >= value + template< + dimension_detail::dimension_t LeftDimension, dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator>=(const LeftDimension& lhs, const T& rhs) noexcept -> auto + { + return lhs.greater_equal(rhs); + } + + // =========================================================================== + // operator< + + // dimension < dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator<(const LeftDimension& lhs, const RightDimension& rhs) noexcept -> auto + { + return lhs.less_than(rhs); + } + + // dimension < value + template< + dimension_detail::dimension_t LeftDimension, dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator<(const LeftDimension& lhs, const T& rhs) noexcept -> auto + { + return lhs.less_than(rhs); + } + + // =========================================================================== + // operator<= + + // dimension <= dimension / dimension_like + template< + dimension_detail::dimension_t LeftDimension, dimension_detail::compatible_dimension_or_dimension_like_t RightDimension + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator<=(const LeftDimension& lhs, const RightDimension& rhs) noexcept -> auto + { + return lhs.less_equal(rhs); + } + + // dimension <= value + template< + dimension_detail::dimension_t LeftDimension, dimension_detail::compatible_value_type_t T + > + requires( + dimension_detail::operation_supported_t + ) + [[nodiscard]] constexpr auto operator<=(const LeftDimension& lhs, const T& rhs) noexcept -> auto + { + return lhs.less_equal(rhs); + } + + // fixme(OPT): std::totally_ordered_with => [dimension_like `op` dimension] / [value `op` dimension] +} + +namespace std +{ + template + struct tuple_element> // NOLINT(cert-dcl58-cpp) + { + using type = T; + }; + + template + struct tuple_size> : std::integral_constant {}; // NOLINT(cert-dcl58-cpp) +} + +#include diff --git a/src/meta/enumeration.ixx b/src/meta/enumeration.hpp similarity index 84% rename from src/meta/enumeration.ixx rename to src/meta/enumeration.hpp index 26f5c435..86876b77 100644 --- a/src/meta/enumeration.ixx +++ b/src/meta/enumeration.hpp @@ -1,55 +1,43 @@ // This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal +// Copyright (C) 2022-2025 Life4gal // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.meta:enumeration; - -import std; - -#else #pragma once #include -#include #include #include #include #include +#include +#include +#include #include -#endif +#include -enum class DummyEnumDoNotPutIntoAnyNamespace +enum class GalPrometheusMetaEnumerationEnum123456789987654321: std::uint8_t { - DO_NOT_USE + _ }; namespace gal::prometheus::meta { namespace enumeration_detail { - // ReSharper disable once CppTemplateParameterNeverUsed - template // DO NOT REMOVE `Vs` - [[nodiscard]] constexpr auto get_full_function_name() noexcept -> std::string_view { return std::source_location::current().function_name(); } - template requires std::is_enum_v> [[nodiscard]] constexpr auto name_of() noexcept -> std::string_view { #if defined(GAL_PROMETHEUS_COMPILER_MSVC) // MSVC - // class std::basic_string_view > `__calling_convention` `namespace`::get_full_function_name<`DummyEnumDoNotPutIntoAnyNamespace::DO_NOT_USE`>(void) noexcept - constexpr std::string_view full_function_name = get_full_function_name(); + // class std::basic_string_view > `__calling_convention` `namespace`::get_full_function_name<`GalPrometheusMetaEnumerationEnum123456789987654321::_`>(void) noexcept + constexpr std::string_view full_function_name = meta::get_full_function_name(); constexpr auto full_function_name_size = full_function_name.size(); - constexpr std::string_view dummy_enum_value_name = "DummyEnumDoNotPutIntoAnyNamespace::DO_NOT_USE"; + constexpr std::string_view dummy_enum_value_name = "GalPrometheusMetaEnumerationEnum123456789987654321::_"; constexpr auto dummy_enum_value_name_size = std::ranges::size(dummy_enum_value_name); // class std::basic_string_view > `__calling_convention` `namespace`::get_full_function_name< @@ -60,11 +48,11 @@ namespace gal::prometheus::meta constexpr auto full_function_name_suffix_size = full_function_name_size - full_function_name_prefix_size - dummy_enum_value_name_size; #elif defined(GAL_PROMETHEUS_COMPILER_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) // CLANG/CLANG-CL - // std::string_view `namespace`::get_full_function_name() [V = `DummyEnumDoNotPutIntoAnyNamespace::DO_NOT_USE`] - constexpr std::string_view full_function_name = get_full_function_name(); + // std::string_view `namespace`::get_full_function_name() [V = `GalPrometheusMetaEnumerationEnum123456789987654321::_`] + constexpr std::string_view full_function_name = meta::get_full_function_name(); constexpr auto full_function_name_size = full_function_name.size(); - constexpr std::string_view dummy_struct_name{"DummyEnumDoNotPutIntoAnyNamespace::DO_NOT_USE"}; + constexpr std::string_view dummy_struct_name{"GalPrometheusMetaEnumerationEnum123456789987654321::_"}; constexpr auto dummy_struct_name_size = std::ranges::size(dummy_struct_name); // std::string_view `namespace`::get_full_function_name() [V = @@ -75,11 +63,11 @@ namespace gal::prometheus::meta constexpr auto full_function_name_suffix_size = full_function_name_size - full_function_name_prefix_size - dummy_struct_name_size; #else // GCC - // constexpr std::string_view `namespace`::get_full_function_name() [with auto = `DummyEnumDoNotPutIntoAnyNamespace::DO_NOT_USE`; std::string_view = std::basic_string_view] - constexpr std::string_view full_function_name = get_full_function_name(); + // constexpr std::string_view `namespace`::get_full_function_name() [with auto = `GalPrometheusMetaEnumerationEnum123456789987654321::_`; std::string_view = std::basic_string_view] + constexpr std::string_view full_function_name = meta::get_full_function_name(); constexpr auto full_function_name_size = full_function_name.size(); - constexpr std::string_view dummy_struct_name{"DummyEnumDoNotPutIntoAnyNamespace::DO_NOT_USE"}; + constexpr std::string_view dummy_struct_name{"GalPrometheusMetaEnumerationEnum123456789987654321::_"}; constexpr auto dummy_struct_name_size = std::ranges::size(dummy_struct_name); // std::string_view `namespace`::get_full_function_name() [V = @@ -95,7 +83,7 @@ namespace gal::prometheus::meta GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(-Wenum-constexpr-conversion) #endif - auto full_name = get_full_function_name(); + auto full_name = meta::get_full_function_name(); #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP @@ -106,56 +94,6 @@ namespace gal::prometheus::meta return full_name; } - template - requires std::is_enum_v> - [[nodiscard]] constexpr auto is_valid_enum() noexcept -> bool - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH - GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(-Wenum-constexpr-conversion) - #endif - - constexpr auto name = name_of(); - - // MSVC - // (enum MyEnum)0x1 - // `anonymous-namespace'::(enum MyEnum)0x1 - // CLANG - // (MyEnum)1 - // (anonymous namespace)::(MyEnum)1 - #if defined(GAL_PROMETHEUS_COMPILER_MSVC) - return not name.starts_with('('); - #elif defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - if (name.starts_with("(anonymous namespace)")) - { - return not name.starts_with("(anonymous namespace)::("); - } - return not name.starts_with('('); - #else - #error "fixme" - #endif - - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP - #endif - } - - template - requires std::is_enum_v - [[nodiscard]] constexpr auto is_valid_enum() noexcept -> bool // - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH - GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(-Wenum-constexpr-conversion) - #endif - - return is_valid_enum(EnumValue)>(); - - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP - #endif - } - template requires std::is_enum_v [[nodiscard]] constexpr auto default_min_value() noexcept -> std::underlying_type_t @@ -187,11 +125,39 @@ namespace gal::prometheus::meta return static_cast(255); } } - } // namespace enumeration_detail - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN + template + struct has_magic_enum_value : std::false_type {}; + + template + requires requires { T::prometheus_magic_enum_flag; } + struct has_magic_enum_value : std::true_type + { + constexpr static auto magic = T::prometheus_magic_enum_flag; + }; + + template + requires requires { T::PrometheusMagicEnumFlag; } + struct has_magic_enum_value : std::true_type + { + constexpr static auto magic = T::PrometheusMagicEnumFlag; + }; + + template + requires requires { T::PROMETHEUS_MAGIC_ENUM_FLAG; } + struct has_magic_enum_value : std::true_type + { + constexpr static auto magic = T::PROMETHEUS_MAGIC_ENUM_FLAG; + }; + + template + constexpr auto has_magic_enum_value_v = has_magic_enum_value::value; - enum class EnumNamePolicy + template + concept magic_enum_value_t = has_magic_enum_value_v; + } + + enum class EnumNamePolicy : std::uint8_t { // namespace_A::namespace_B::namespace_C::enum_name::Value // scoped enum // namespace_A::namespace_B::namespace_C::Value @@ -212,8 +178,7 @@ namespace gal::prometheus::meta * constexpr static auto value = EnumNamePolicy::VALUE_ONLY; * }; */ - template - requires std::is_enum_v + template struct enum_name_policy { constexpr static auto value = EnumNamePolicy::FULL; @@ -228,7 +193,6 @@ namespace gal::prometheus::meta * }; */ template - requires std::is_enum_v struct enum_range { constexpr static auto min = enumeration_detail::default_min_value(); @@ -237,13 +201,31 @@ namespace gal::prometheus::meta /** * template<> - * struct enum_range : std::true_type + * struct enum_is_flag : std::true_type * { * }; + * + * OR + * + * enum MyEnum + * { + * E1 = 0, + * E2 = 1, + * E3 = 2, + * ... + * + * prometheus_magic_enum_flag + * // or + * PrometheusMagicEnumFlag + * // or + * PROMETHEUS_MAGIC_ENUM_FLAG + * } */ - template - requires std::is_enum_v - struct enum_is_flag {}; + template + struct enum_is_flag : std::false_type {}; + + template + struct enum_is_flag : std::true_type {}; /** * template<> @@ -258,7 +240,7 @@ namespace gal::prometheus::meta /** * template<> - * struct enum_name + * struct enum_value_name * { * constexpr static std::string_view value{"MY-ENUM-VALUE"}; * }; @@ -268,10 +250,72 @@ namespace gal::prometheus::meta struct enum_value_name {}; } - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END - namespace enumeration_detail { + template + requires std::is_enum_v> + [[nodiscard]] constexpr auto is_valid_enum() noexcept -> bool + { + // skip the `magic` + if constexpr (has_magic_enum_value_v>) + { + if constexpr (EnumValue == has_magic_enum_value>::magic) + { + return false; + } + } + + #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(-Wenum-constexpr-conversion) + #endif + + constexpr auto name = name_of(); + + // MSVC + // (enum MyEnum)0x1 + // `anonymous-namespace'::(enum MyEnum)0x1 + // CLANG + // (MyEnum)1 + // (anonymous namespace)::(MyEnum)1 + // GNU + // (MyEnum)1 + // (::MyEnum)1 + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + return not name.starts_with('('); + #elif defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) + if (name.starts_with("(anonymous namespace)")) + { + return not name.starts_with("(anonymous namespace)::("); + } + return not name.starts_with('('); + #elif defined(GAL_PROMETHEUS_COMPILER_GNU) + return not name.starts_with('('); + #else + #error "fixme" + #endif + + #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP + #endif + } + + template + requires std::is_enum_v + [[nodiscard]] constexpr auto is_valid_enum() noexcept -> bool // + { + #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(-Wenum-constexpr-conversion) + #endif + + return is_valid_enum(EnumValue)>(); + + #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP + #endif + } + template requires std::is_enum_v [[nodiscard]] constexpr auto flag_get_bits_combination() noexcept -> std::underlying_type_t @@ -279,10 +323,14 @@ namespace gal::prometheus::meta using type = std::underlying_type_t; constexpr auto index_sequence = std::views::iota(static_cast(0), static_cast(std::numeric_limits::digits)); - constexpr auto index_chunk = index_sequence | std::views::chunk(Count); + constexpr auto index_chunk = index_sequence | std::views::chunk(static_cast(Count)); constexpr auto indices = index_chunk[Index]; - constexpr auto bits = std::ranges::fold_left(indices, static_cast(0), [](const auto total, const auto current) noexcept -> auto { return total | (type{1} << current); }); + constexpr auto bits = std::ranges::fold_left( + indices, + static_cast(0), + [](const auto total, const auto current) noexcept -> auto { return total | (type{1} << current); } + ); return bits; } @@ -296,7 +344,7 @@ namespace gal::prometheus::meta std::vector values{}; // zero - if constexpr (enumeration_detail::is_valid_enum()) + if constexpr (is_valid_enum()) { values.push_back(static_cast(0)); } @@ -308,7 +356,7 @@ namespace gal::prometheus::meta const auto check = [&values]() noexcept -> void { if constexpr (constexpr auto bits = flag_get_bits_combination(); - enumeration_detail::is_valid_enum()) + is_valid_enum()) { values.push_back(static_cast(bits)); } @@ -335,14 +383,14 @@ namespace gal::prometheus::meta requires std::is_enum_v [[nodiscard]] constexpr auto is_flag_with_user_defined() noexcept -> bool { - if constexpr (requires { user_defined::enum_is_flag::value; }) + if constexpr (user_defined::enum_is_flag::value) { - return user_defined::enum_is_flag::value; + return true; } else { // We require at least 6 values (1 << 6 => 64) to be considered for inferring a flag. - return cached_flag_dynamic_enum_values_size::value >= 6; + return cached_flag_dynamic_enum_values_size::value >= static_cast::value_type>(6); } } @@ -354,7 +402,11 @@ namespace gal::prometheus::meta { return user_defined::enum_name::value; } - else { return meta::name_of(); } + else + { + // name::name_of + return name_of(); + } } template @@ -365,7 +417,7 @@ namespace gal::prometheus::meta { return user_defined::enum_value_name::value; } - else { return enumeration_detail::name_of(); } + else { return name_of(); } } template< @@ -580,8 +632,8 @@ namespace gal::prometheus::meta typename return_type::value_type{ // flag_dynamic_enum_values()[Index], - enumeration_detail::trim_full_name( - enumeration_detail::name_of(flag_dynamic_enum_values()[Index])>() + trim_full_name( + name_of(flag_dynamic_enum_values()[Index])>() ) }... }; @@ -609,8 +661,8 @@ namespace gal::prometheus::meta // typename return_type::value_type{ static_cast(min + Index), - enumeration_detail::trim_full_name( - enumeration_detail::name_of(min + Index)>() + trim_full_name( + name_of(min + Index)>() ) }... }; @@ -622,8 +674,6 @@ namespace gal::prometheus::meta constexpr auto names_of_enum = generate_enum_names(); } - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - template requires std::is_enum_v [[nodiscard]] constexpr auto min_value_of() noexcept -> std::underlying_type_t @@ -799,7 +849,15 @@ namespace gal::prometheus::meta if (std::popcount(unsigned_value) == 1) { const auto i = std::countr_zero(unsigned_value); - result.append_range(name_of(1 << i)); + if constexpr (requires { result.append_range(name_of(1 << i)); }) + { + result.append_range(name_of(1 << i)); + } + else + { + result.append(name_of(1 << i)); + } + return result; } @@ -807,8 +865,16 @@ namespace gal::prometheus::meta { if (value & (1 << i)) { - result.append_range(name_of(1 << i)); - result.append_range(split); + if constexpr (requires { result.append_range(name_of(1 << i)); }) + { + result.append_range(name_of(1 << i)); + result.append_range(split); + } + else + { + result.append(name_of(1 << i)); + result.append(split); + } } } @@ -881,11 +947,12 @@ namespace gal::prometheus::meta { constexpr auto list = names_of(); + // error C2662: `const std::ranges::split_view>,std::basic_string_view>>` => `std::ranges::split_view>,std::basic_string_view>> &` // ReSharper disable once CppLocalVariableMayBeConst auto names = enum_name | std::views::split(split); auto result = std::to_underlying(empty); - for (const auto each: names) + for (const auto& each: names) { const std::string_view s{each}; @@ -919,32 +986,22 @@ namespace gal::prometheus::meta template::value, bool Strict = true> requires std::is_enum_v - [[nodiscard]] constexpr auto value_of( - const std::string_view enum_name, - const std::string_view split - ) noexcept -> EnumType + [[nodiscard]] constexpr auto value_of(const std::string_view enum_name, const std::string_view split) noexcept -> EnumType { return meta::value_of(enum_name, static_cast(0), split); } template::value, bool Strict = true> requires std::is_enum_v - [[nodiscard]] constexpr auto value_of( - const std::string_view enum_name, - const EnumType empty - ) noexcept -> EnumType + [[nodiscard]] constexpr auto value_of(const std::string_view enum_name, const EnumType empty) noexcept -> EnumType { return meta::value_of(enum_name, empty, "|"); } template::value, bool Strict = true> requires std::is_enum_v - [[nodiscard]] constexpr auto value_of( - const std::string_view enum_name - ) noexcept -> EnumType + [[nodiscard]] constexpr auto value_of(const std::string_view enum_name) noexcept -> EnumType { return meta::value_of(enum_name, static_cast(0), "|"); } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END } diff --git a/src/meta/member.hpp b/src/meta/member.hpp new file mode 100644 index 00000000..653ef79e --- /dev/null +++ b/src/meta/member.hpp @@ -0,0 +1,519 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include + +#include + +#include +#include + +namespace gal::prometheus::meta +{ + template + struct extern_accessor + { + [[nodiscard]] constexpr static auto make() noexcept -> T + { + return T{}; + } + }; + + namespace member_detail + { + // note that this requires the target type to be default constructible + template + extern const auto extern_any = extern_accessor::make(); + + template + struct placeholder + { + constexpr explicit(false) placeholder(auto&&...) noexcept {} + }; + + struct any + { + template + // ReSharper disable once CppFunctionIsNotImplemented + // ReSharper disable once CppNonExplicitConversionOperator + constexpr explicit(false) operator T() const noexcept; + }; + + template + struct any_except_base_of + { + template + requires(not std::is_base_of_v) + // ReSharper disable once CppFunctionIsNotImplemented + // ReSharper disable once CppNonExplicitConversionOperator + constexpr explicit(false) operator U() const noexcept; + }; + + template + struct wrapper + { + T& ref; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + }; + + template + wrapper(T&) -> wrapper; + + template + constexpr auto is_tuple_structured_binding_v = false; + + template + requires requires + { + // lazy + // ReSharper disable once CppUseTypeTraitAlias + std::tuple_size::value; + // ReSharper disable once CppUseTypeTraitAlias + typename std::tuple_element<0, T>::type; + } and (requires(const T& tuple) + { + // member function + tuple.template get<0>(); + } or + requires(const T& tuple) + { + // free function + get<0>(tuple); + } + ) + constexpr auto is_tuple_structured_binding_v = true; + + template + constexpr auto is_aggregate_structured_binding_v = false; + + template + requires std::is_aggregate_v + constexpr auto is_aggregate_structured_binding_v = true; + + template + constexpr auto is_structured_binding_v = is_tuple_structured_binding_v or is_aggregate_structured_binding_v; + + template + concept structured_binding_t = is_structured_binding_v; + + constexpr auto member_size_unknown = std::numeric_limits::max(); + + template + [[nodiscard]] constexpr auto member_size_impl() noexcept -> std::size_t + { + // the user defines tuple_element/tuple_size/get, and we believe the user + if constexpr (is_tuple_structured_binding_v) + { + return std::tuple_size_v; + } + // the user does not define tuple_element/tuple_size/get, but the target type supports aggregate initialization. + else if constexpr (is_aggregate_structured_binding_v) + { + // the minimum size of each type is 1 (at least at this point), so the number of arguments cannot be larger than the type size + if constexpr (sizeof...(Args) > sizeof(T)) + { + return member_size_unknown; + } + else if constexpr ( + requires { T{Args{}...}; } and + not requires { T{Args{}..., any{}}; } + ) + { + return (0 + ... + std::is_same_v>); + } + else if constexpr ( + requires { T{Args{}...}; } and + not requires { T{Args{}..., any_except_base_of{}}; } + ) + { + return member_size_impl(); + } + else + { + return member_size_impl>(); + } + } + else + { + return member_size_unknown; + } + } + + template + [[nodiscard]] constexpr auto member_size() noexcept -> std::size_t + { + return member_size_impl>(); + } + + template + constexpr auto is_known_member_size_v = false; + + template + requires (member_size() != member_size_unknown) + constexpr auto is_known_member_size_v = true; + + template + concept known_member_size_t = is_known_member_size_v; + + template + concept known_member_t = known_member_size_t and structured_binding_t; + + template + constexpr auto nth_element_impl = [](std::index_sequence) noexcept -> decltype(auto) + { + return [](placeholder&&... p, NthType&& nth, auto&&...) noexcept -> decltype(auto) + { + ((std::ignore = std::move(p)), ...); + + return std::forward(nth); + }; + }(std::make_index_sequence{}); + + template + requires (N < sizeof...(Args)) + [[nodiscard]] constexpr auto nth_element(Args&&... args) noexcept -> decltype(auto) + { + return nth_element_impl(std::forward(args)...); + } + + // member.visit.inl + template + [[nodiscard]] constexpr auto visit(Function&& function, T&& object) noexcept -> decltype(auto); + + template + constexpr auto invoke(const Function& function, Args&&... args) noexcept -> decltype(auto) + { + // function(args...) + if constexpr (requires { function.template operator()(std::forward(args)...); }) + { + return function.template operator()(std::forward(args)...); + } + // function(args...) + else if constexpr (requires { function(std::forward(args)...); }) + { + return function(std::forward(args)...); + } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + } + + template + concept known_member_t = member_detail::known_member_t; + + template + [[nodiscard]] constexpr auto member_size() noexcept -> std::size_t + { + return member_detail::member_size(); + } + + template + requires (known_member_t> and N < member_size>()) + [[nodiscard]] constexpr auto member_of_index(T&& object) noexcept -> decltype(auto) + { + using bare_type = std::remove_cvref_t; + if constexpr (member_detail::is_tuple_structured_binding_v) + { + if constexpr (requires { std::forward(object).template get(); }) + { + return std::forward(object).template get(); + } + else if constexpr (requires { get(std::forward(object)); }) + { + return get(std::forward(object)); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + else if constexpr (member_detail::is_aggregate_structured_binding_v) + { + return member_detail::visit( + [](Ts&&... args) noexcept -> decltype(auto) + { + return member_detail::nth_element(std::forward(args)...); + }, + std::forward(object) + ); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + namespace member_detail + { + template + requires (member_detail::known_member_t> and N < member_size>()) + struct member_type_of_index + { + using type = std::decay_t(std::declval()))>; + }; + } + + template + using member_type_of_index = typename member_detail::member_type_of_index::type; + + template + requires (known_member_t> and N < member_size>()) + [[nodiscard]] constexpr auto name_of_member() noexcept -> std::string_view + { + constexpr auto full_function_name = get_full_function_name< + member_detail::visit( + [](Ts&&... args) noexcept -> auto // + { + return member_detail::wrapper + { + member_detail::nth_element(std::forward(args)...) + }; + }, + member_detail::extern_any>) // + >(); + + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + // MSVC + // class std::basic_string_view > `__calling_convention` `namespace`::get_full_function_name{const `member_type`&:`namespace`::member_name::extern_any->`member_name`}>(void) noexcept + // constexpr auto full_function_name_size = full_function_name.size(); + + constexpr std::string_view splitter{">->"}; + constexpr auto splitter_size = splitter.size(); + + // class std::basic_string_view > `__calling_convention` `namespace`::get_full_function_name{const `member_type`&:`namespace`::member_name::extern_any-> + static_assert(full_function_name.find(splitter) != std::string_view::npos); + constexpr auto full_function_name_prefix_size = full_function_name.find(splitter) + splitter_size; + + // }>(void) noexcept + constexpr std::string_view suffix{"}>(void) noexcept"}; + constexpr auto full_function_name_suffix_size = suffix.size(); + #elif defined(GAL_PROMETHEUS_COMPILER_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) + // CLANG/CLANG-CL + // std::string_view `namespace`::get_full_function_name() [Vs = ::_Call(static_cast<(lambda at `ABS_FILE_PATH`\member_name.hpp:413:6) &>(_Obj), static_cast(_Arg1))){extern_any.`member_name`}>] + // constexpr auto full_function_name_size = full_function_name.size(); + + constexpr std::string_view splitter{"extern_any."}; + constexpr auto splitter_size = splitter.size(); + + // std::string_view `namespace`::get_full_function_name() [Vs = ::_Call(static_cast<(lambda at `ABS_FILE_PATH`\member_name.hpp:413:6) &>(_Obj), static_cast(_Arg1))){extern_any. + static_assert(full_function_name.find(splitter) != std::string_view::npos); + constexpr auto full_function_name_prefix_size = full_function_name.find(splitter) + splitter_size; + + // }>] + constexpr std::string_view suffix{"}>]"}; + constexpr auto full_function_name_suffix_size = suffix.size(); + #else + // GCC + // constexpr std::string_view `namespace`::get_full_function_name() [with auto ... = {`namespace`::member_name::wrapper{`namespace`::member_name::extern_any<`my_struct`>.`my_struct`::`member_name`}}; std::string_view = std::basic_string_view] + // constexpr auto full_function_name_size = full_function_name.size(); + + // fixme: find a suitable splitter. + // extern_any<`my_struct`>.`my_struct`::`member_name` + constexpr std::string_view type_name = name_of>(); + constexpr auto type_name_size = type_name.size() + 2; // 2 == `::` + constexpr std::string_view splitter{">."}; + constexpr auto splitter_size = splitter.size(); + + // constexpr std::string_view `namespace`::get_full_function_name() [with auto ... = {`namespace`::member_name::wrapper{`namespace`::member_name::extern_any<`my_struct`>.`my_struct`:: + static_assert(full_function_name.find(splitter) != std::string_view::npos); + constexpr auto full_function_name_prefix_size = full_function_name.find(splitter) + splitter_size + type_name_size; + + // }}; std::string_view = std::basic_string_view] + constexpr std::string_view suffix{"}}; std::string_view = std::basic_string_view]"}; + constexpr auto full_function_name_suffix_size = suffix.size(); + #endif + + auto name = full_function_name; + name.remove_prefix(full_function_name_prefix_size); + name.remove_suffix(full_function_name_suffix_size); + return name; + } + + constexpr auto member_index_unknown = std::numeric_limits::max(); + + namespace member_detail + { + template + [[nodiscard]] constexpr auto member_index() noexcept -> std::size_t + { + return [](std::index_sequence) noexcept -> std::size_t + { + const auto f = []() noexcept + { + if constexpr (name_of_member() == Name) + { + return I; + } + else + { + return member_index_unknown; + } + }; + + std::size_t index; + + // warning : expression result unused [-Wunused-value] + // (((index = f.template operator()()) == member_index_unknown) and ...); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^ + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH + GAL_PROMETHEUS_COMPILER_DISABLE_CLANG_WARNING(-Wunused-value) + + (((index = f.template operator()()) == member_index_unknown) and ...); + + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP + + return index; + }(std::make_index_sequence()>{}); + } + + template + [[nodiscard]] constexpr auto member_index(const std::string_view name) noexcept -> std::size_t + { + return [name](std::index_sequence) noexcept -> std::size_t + { + const auto f = [name]() noexcept + { + if (name_of_member() == name) + { + return I; + } + + return member_index_unknown; + }; + + std::size_t index; + + // warning : expression result unused [-Wunused-value] + // (((index = f.template operator()()) == member_index_unknown) and ...); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^ + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH + GAL_PROMETHEUS_COMPILER_DISABLE_CLANG_WARNING(-Wunused-value) + + (((index = f.template operator()()) == member_index_unknown) and ...); + + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP + + return index; + }(std::make_index_sequence()>{}); + } + } + + template + requires (known_member_t>) + [[nodiscard]] constexpr auto member_index() noexcept -> std::size_t + { + return member_detail::member_index(); + } + + template + requires (known_member_t>) + [[nodiscard]] constexpr auto member_index(const std::string_view name) noexcept -> std::size_t + { + return member_detail::member_index(name); + } + + template + requires (known_member_t>) + [[nodiscard]] constexpr auto has_member() noexcept -> bool + { + return member_index() != member_index_unknown; + } + + template + requires (known_member_t>) + [[nodiscard]] constexpr auto has_member(const std::string_view name) noexcept -> bool + { + return member_index(name) != member_index_unknown; + } + + namespace member_detail + { + enum class FoldCategory : std::uint8_t + { + ALL, + UNTIL_FALSE, + }; + + template + constexpr auto member_walk( + const Function& function, + T&& object, + Ts&&... optional_objects + ) noexcept -> void + { + [&function](std::index_sequence, Us&&... os) noexcept -> void + { + const auto f = [&function](Os&&... o) noexcept -> decltype(auto) + { + return member_detail::invoke(function, meta::member_of_index(std::forward(o))...); + }; + + if constexpr (Category == FoldCategory::ALL) + { + (f.template operator()(std::forward(os)...), ...); + } + else if constexpr (Category == FoldCategory::UNTIL_FALSE) + { + (f.template operator()(std::forward(os)...) and ...); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + }(std::make_index_sequence>()>{}, std::forward(object), std::forward(optional_objects)...); + } + } + + template + requires + ( + known_member_t> and + ( + sizeof...(Ts) == 0 or + ( + ( + known_member_t> and + member_size>() == member_size>() + ) + and ... + ) + ) + ) + constexpr auto member_walk( + const Function& function, + T&& object, + Ts&&... optional_objects + ) noexcept -> void + { + member_detail::member_walk(function, std::forward(object), std::forward(optional_objects)...); + } + + template + requires + ( + known_member_t> and + ( + sizeof...(Ts) == 0 or + ( + ( + known_member_t> and + member_size>() == member_size>() + ) + and ... + ) + ) + ) + constexpr auto member_walk_until( + const Function& function, + T&& object, + Ts&&... optional_objects + ) noexcept -> void + { + member_detail::member_walk(function, std::forward(object), std::forward(optional_objects)...); + } +} + +#include diff --git a/src/meta/member.ixx b/src/meta/member.ixx deleted file mode 100644 index 9652d57d..00000000 --- a/src/meta/member.ixx +++ /dev/null @@ -1,3342 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.meta:member; - -import std; -import :string; - -#else -#pragma once - -#include -#include -#include -#include - -#include -#include - -#endif - -namespace gal::prometheus::meta -{ - namespace member_detail - { - // fixme: dup - // ReSharper disable once CppTemplateParameterNeverUsed - template // DO NOT REMOVE `Vs` - [[nodiscard]] constexpr auto get_full_function_name() noexcept -> std::string_view { return std::source_location::current().function_name(); } - - template - extern const T extern_any{}; - - template - struct placeholder - { - constexpr explicit(false) placeholder(auto&&...) noexcept {} - }; - - struct any - { - template - // ReSharper disable once CppFunctionIsNotImplemented - // ReSharper disable once CppNonExplicitConversionOperator - constexpr explicit(false) operator T() const noexcept; - }; - - template - struct any_except_base_of - { - template - requires(not std::is_base_of_v) - constexpr explicit(false) operator U() const noexcept; - }; - - template - struct wrapper - { - T& ref; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) - }; - - template - wrapper(T&) -> wrapper; - - template - struct is_tuple_like : std::false_type {}; - - template - requires requires - { - // lazy - // ReSharper disable once CppUseTypeTraitAlias - std::tuple_size::value; - // ReSharper disable once CppUseTypeTraitAlias - typename std::tuple_element<0, T>::type; - } and (requires(const T& t) - { - // member function - t.template get<0>(); - } or - requires(const T& t) - { - // free function - get<0>(t); - } - ) - struct is_tuple_like : std::true_type {}; - - template - constexpr auto is_tuple_like_v = is_tuple_like::value; - - template - constexpr auto is_member_element_gettable_v = - // user-defined structured-binding - is_tuple_like_v or - // auto structured-binding - std::is_aggregate_v; - - template - concept member_element_gettable_t = is_member_element_gettable_v; - - constexpr auto member_size_unknown = std::numeric_limits::max(); - - template - [[nodiscard]] constexpr auto member_size_impl() noexcept -> std::size_t // - { - if constexpr (is_tuple_like_v) { return std::tuple_size_v; } - else if constexpr (std::is_aggregate_v) - { - if constexpr (sizeof...(Args) > sizeof(T)) { return member_size_unknown; } - else if constexpr (requires { T{Args{}...}; } and not requires { T{Args{}..., any{}}; }) // - { - return (0 + ... + std::is_same_v>); - } - else if constexpr (requires { T{Args{}...}; } and not requires { T{Args{}..., any_except_base_of{}}; }) // - { - return member_size_impl(); - } - else // - { - return member_size_impl>(); - } - } - else { return member_size_unknown; } - } - - template - [[nodiscard]] constexpr auto member_size() noexcept -> std::size_t // - { - return member_size_impl>(); - } - - template - [[nodiscard]] constexpr auto member_size(T&&) noexcept -> std::size_t // - { - return member_size>(); - } - - template - constexpr auto is_member_size_gettable_v = member_size() != member_size_unknown; - template - concept member_size_gettable_t = is_member_size_gettable_v; - - template - constexpr auto is_member_gettable_v = is_member_element_gettable_v and is_member_size_gettable_v; - template - concept member_gettable_t = // - member_element_gettable_t and - member_size_gettable_t; - - template - requires member_gettable_t> - [[nodiscard]] constexpr auto visit(Function&& function, T&& value) noexcept -> decltype(auto) - { - #define MEMBER_NAME_VISIT_DO_FORWARD(...) static_cast(__VA_ARGS__) // std::forward - #define MEMBER_NAME_VISIT_DO_FORWARD_LIKE(...) static_cast, std::add_lvalue_reference_t>, std::add_rvalue_reference_t>>>(__VA_ARGS__)// forward like - - - - #if __cpp_structured_bindings >= 202601L - auto&& [... vs] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke(MEMBER_NAME_VISIT_DO_FORWARD(function), MEMBER_NAME_VISIT_DO_FORWARD_LIKE(vs)...); - #else - if constexpr (constexpr auto size = member_size(); - size == 1) - { - auto&& [m0] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0) - ); - } - else if constexpr (size == 2) - { - auto&& [m0, m1] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1) - ); - } - else if constexpr (size == 3) - { - auto&& [m0, m1, m2] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2) - ); - } - else if constexpr (size == 4) - { - auto&& [m0, m1, m2, m3] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3) - ); - } - else if constexpr (size == 5) - { - auto&& [m0, m1, m2, m3, m4] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4) - ); - } - else if constexpr (size == 6) - { - auto&& [m0, m1, m2, m3, m4, m5] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5) - ); - } - else if constexpr (size == 7) - { - auto&& [m0, m1, m2, m3, m4, m5, m6] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6) - ); - } - else if constexpr (size == 8) - { - auto&& [m0, m1, m2, m3, m4, m5, m6, m7] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7) - ); - } - else if constexpr (size == 9) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8) - ); - } - else if constexpr (size == 10) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9) - ); - } - else if constexpr (size == 11) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10) - ); - } - else if constexpr (size == 12) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11) - ); - } - else if constexpr (size == 13) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12) - ); - } - else if constexpr (size == 14) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13) - ); - } - else if constexpr (size == 15) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13,m14 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14) - ); - } - else if constexpr (size == 16) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13,m14, m15 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15) - ); - } - else if constexpr (size == 17) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13,m14, m15, - m16 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16) - ); - } - else if constexpr (size == 18) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13,m14, m15, - m16, m17 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17) - ); - } - else if constexpr (size == 19) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13,m14, m15, - m16, m17, m18 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18) - ); - } - else if constexpr (size == 20) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13,m14, m15, - m16, m17, m18, m19 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19) - ); - } - else if constexpr (size == 21) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13,m14, m15, - m16, m17, m18, m19,m20 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20) - ); - } - else if constexpr (size == 22) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6,m7, - m8, m9, m10, m11, m12, m13,m14, m15, - m16, m17, m18, m19,m20, m21 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21) - ); - } - else if constexpr (size == 23) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13,m14, m15, - m16, m17, m18, m19,m20, m21, m22 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22) - ); - } - else if constexpr (size == 24) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13,m14, m15, - m16, m17, m18, m19,m20, m21, m22, m23 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23) - ); - } - else if constexpr (size == 25) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13,m14, m15, - m16, m17, m18, m19,m20, m21, m22, m23, - m24 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24) - ); - } - else if constexpr (size == 26) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6,m7, - m8, m9, m10, m11, m12, m13,m14, m15, - m16, m17, m18, m19,m20, m21, m22, m23, - m24, m25 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25) - ); - } - else if constexpr (size == 27) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13,m14, m15, - m16, m17, m18, m19,m20, m21, m22, m23, - m24, m25,m26 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26) - ); - } - else if constexpr (size == 28) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6,m7, - m8, m9, m10, m11, m12, m13,m14, m15, - m16, m17, m18, m19,m20, m21, m22, m23, - m24, m25,m26,m27 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27) - ); - } - else if constexpr (size == 29) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13,m14, m15, - m16, m17, m18, m19,m20, m21, m22, m23, - m24, m25,m26,m27, m28 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28) - ); - } - else if constexpr (size == 30) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6,m7, - m8, m9, m10, m11, m12, m13,m14, m15, - m16, m17, m18, m19,m20, m21, m22, m23, - m24, m25,m26,m27, m28, m29 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29) - ); - } - else if constexpr (size == 31) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, - m7, m8, m9, m10, m11, m12, m13,m14, m15, - m16, m17, m18, m19,m20, m21, m22, m23, - m24, m25,m26,m27, m28, m29, m30 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30) - ); - } - else if constexpr (size == 32) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13,m14, m15, - m16, m17, m18, m19,m20, m21, m22, m23, - m24, m25,m26,m27, m28, m29, m30, m31 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31) - ); - } - else if constexpr (size == 33) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6,m7, - m8, m9, m10, m11, m12, m13,m14, m15, - m16, m17, m18, m19,m20, m21, m22, m23, - m24, m25,m26,m27, m28, m29, m30, m31, - m32 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32) - ); - } - else if constexpr (size == 34) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6,m7, - m8, m9, m10, m11, m12,m13, m14, m15, - m16, m17, m18,m19, m20, m21, m22, m23, - m24,m25, m26,m27, m28, m29, m30, m31, - m32, m33 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33) - ); - } - else if constexpr (size == 35) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34) - ); - } - else if constexpr (size == 36) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35) - ); - } - else if constexpr (size == 37) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36) - ); - } - else if constexpr (size == 38) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37) - ); - } - else if constexpr (size == 39) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38) - ); - } - else if constexpr (size == 40) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39) - ); - } - else if constexpr (size == 41) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40) - ); - } - else if constexpr (size == 42) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, m8, - m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, - m31, m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41) - ); - } - else if constexpr (size == 43) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42) - ); - } - else if constexpr (size == 44) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43) - ); - } - else if constexpr (size == 45) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44) - ); - } - else if constexpr (size == 46) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45) - ); - } - else if constexpr (size == 47) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45, m46 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46) - ); - } - else if constexpr (size == 48) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45, m46, m47 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47) - ); - } - else if constexpr (size == 49) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45, m46, m47, - m48 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48) - ); - } - else if constexpr (size == 50) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45, m46, m47, - m48, m49 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49) - ); - } - else if constexpr (size == 51) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45, m46, m47, - m48, m49, m50 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50) - ); - } - else if constexpr (size == 52) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45, m46, m47, - m48, m49, m50,m51 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51) - ); - } - else if constexpr (size == 53) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45, m46, m47, - m48, m49, m50,m51, m52 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52) - ); - } - else if constexpr (size == 54) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45, m46, m47, - m48, m49, m50, m51, m52,m53 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53) - ); - } - else if constexpr (size == 55) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45, m46, m47, - m48, m49, m50, m51, m52,m53, m54 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54) - ); - } - else if constexpr (size == 56) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45, m46, m47, - m48, m49, m50, m51, m52,m53, m54, m55 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m55) - ); - } - else if constexpr (size == 57) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45, m46, m47, - m48, m49, m50, m51, m52,m53, m54, m55, - m56 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m55), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m56) - ); - } - else if constexpr (size == 58) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45, m46, m47, - m48, m49, m50, m51, m52,m53, m54, m55, - m56, m57 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m55), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m56), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m57) - ); - } - else if constexpr (size == 59) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45, m46, m47, - m48, m49, m50, m51, m52,m53, m54, m55, - m56, m57, m58 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m55), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m56), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m57), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m58) - ); - } - else if constexpr (size == 60) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45, m46, m47, - m48, m49, m50, m51, m52,m53, m54, m55, - m56, m57, m58, m59 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m55), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m56), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m57), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m58), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m59) - ); - } - else if constexpr (size == 61) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45, m46, m47, - m48, m49, m50, m51, m52,m53, m54, m55, - m56, m57, m58, m59, m60 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m55), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m56), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m57), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m58), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m59), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m60) - ); - } - else if constexpr (size == 62) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, m24, - m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45, m46, m47, - m48, m49, m50, m51, m52,m53, m54, m55, - m56, m57, m58, m59, m60, m61 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m55), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m56), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m57), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m58), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m59), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m60), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m61) - ); - } - else if constexpr (size == 63) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45, m46, m47, - m48, m49, m50, m51, m52,m53, m54, m55, - m56, m57, m58, m59, m60, m61, m62 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m55), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m56), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m57), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m58), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m59), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m60), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m61), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m62) - ); - } - else if constexpr (size == 64) - { - auto&& [ - m0, m1, m2, m3, m4, m5, m6, m7, - m8, m9, m10, m11, m12, m13, m14, m15, - m16, m17, m18, m19, m20, m21, m22, m23, - m24, m25, m26,m27, m28, m29, m30, m31, - m32, m33, m34, m35, m36, m37, m38, m39, - m40, m41, m42, m43, m44, m45, m46, m47, - m48, m49, m50, m51, m52,m53, m54, m55, - m56, m57, m58, m59, m60, m61, m62, m63 - ] = MEMBER_NAME_VISIT_DO_FORWARD(value); - return std::invoke( - MEMBER_NAME_VISIT_DO_FORWARD(function), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m55), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m56), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m57), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m58), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m59), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m60), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m61), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m62), - MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m63) - ); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE("too much members."); } - #endif - - #undef MEMBER_NAME_VISIT_DO_FORWARD_LIKE - } - - template - constexpr auto nth_element_impl = [](std::index_sequence) noexcept -> decltype(auto) // - { - return [](placeholder&&..., NthType&& nth, auto&&...) noexcept -> decltype(auto) // - { - return std::forward(nth); - }; - }(std::make_index_sequence{}); - - template - requires (N < sizeof...(Args)) - [[nodiscard]] constexpr auto nth_element(Args&&... args) noexcept -> decltype(auto) // - { - return nth_element_impl(std::forward(args)...); - } - - template - constexpr auto invoke(Function&& function, Args&&... args) noexcept -> void - { - if constexpr (requires { std::forward(function).template operator()(std::forward(args)...); }) // - { - std::forward(function).template operator()(std::forward(args)...); - } - else if constexpr (requires { std::forward(function)(std::forward(args)...); }) // - { - std::forward(function)(std::forward(args)...); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - template - constexpr auto is_member_gettable_v = member_detail::is_member_gettable_v; - template - concept member_gettable_t = member_detail::member_gettable_t; - - template - [[nodiscard]] constexpr auto member_size() noexcept -> std::size_t // - { - return member_detail::member_size(); - } - - template - requires member_gettable_t> - [[nodiscard]] constexpr auto member_of_index(T&& value) noexcept -> decltype(auto) // - { - return member_detail::visit( - [](Ts&&... args) noexcept -> decltype(auto) // - { - return member_detail::nth_element(std::forward(args)...); - }, - std::forward(value)); - } - - template - requires member_gettable_t> - struct member_type_of_index - { - using type = std::decay_t(std::declval()))>; - }; - - template - using member_type_of_index_t = typename member_type_of_index::type; - - template - requires member_gettable_t> - constexpr auto member_walk(Function function, T&& value) noexcept -> void - { - [&function]( - std::index_sequence, - U&& u - ) mutable noexcept -> void // - { - // function(member_of_index(std::forward(u))...); - (member_detail::invoke(function, member_of_index(std::forward(u))), ...); - }(std::make_index_sequence()>{}, std::forward(value)); - } - - template - requires - ( - // type - member_gettable_t> and - (sizeof...(Ts) == 0 or ((member_gettable_t>) and ...)) and - // size - (sizeof...(Ts) == 0 or ((member_size() >= member_size()) and ...)) - ) - constexpr auto member_zip_walk( - Function function, - T&& value, - Ts&&... optional_extra_values - ) noexcept -> void - { - [&function] ( - std::index_sequence, - Us&&... us - ) mutable noexcept -> void // - { - //( - // member_detail::invoke( // - // function, - // member_of_index(std::forward(us))... - // ), // - // ... // - //); - - const auto f = [&function](U&&... u) noexcept -> void // - { - member_detail::invoke(function, member_of_index(std::forward(u))...); - }; - - (f.template operator()(std::forward(us)...), ...); - }(std::make_index_sequence()>{}, std::forward(value), std::forward(optional_extra_values)...); - } - - template - requires - ( - // type - member_gettable_t> and - (sizeof...(Ts) == 0 or ((member_gettable_t>) and ...)) and - // size - (sizeof...(Ts) == 0 or ((member_size() >= member_size()) and ...)) - ) - constexpr auto member_zip_walk_until( - Function function, - T&& value, - Ts&&... optional_extra_values - ) noexcept -> void - { - [&function] ( - std::index_sequence, - Us&&... us - ) mutable noexcept -> void // - { - // ( - // member_detail::invoke( // - // function, - // member_of_index(std::forward(us))... - // ) and // - // ... // - // ); - - const auto f = [&function](U&&... u) noexcept -> void // - { - member_detail::invoke(function, member_of_index(std::forward(u))...); - }; - - (f.template operator()(std::forward(us)...) and ...); - }(std::make_index_sequence()>{}, std::forward(value), std::forward(optional_extra_values)...); - } - - template - requires member_gettable_t> - [[nodiscard]] constexpr auto name_of_member() noexcept -> std::string_view - { - constexpr auto full_function_name = member_detail::get_full_function_name< - member_detail::visit( - [](Ts&&... args) noexcept -> auto // - { - return member_detail::wrapper{member_detail::nth_element(std::forward(args)...)}; - }, - member_detail::extern_any>) // - >(); - - #if defined(GAL_PROMETHEUS_COMPILER_MSVC) - // MSVC - // class std::basic_string_view > `__calling_convention` `namespace`::get_full_function_name{const `member_type`&:`namespace`::member_name::extern_any->`member_name`}>(void) noexcept - // constexpr auto full_function_name_size = full_function_name.size(); - - constexpr std::string_view splitter{">->"}; - constexpr auto splitter_size = splitter.size(); - - // class std::basic_string_view > `__calling_convention` `namespace`::get_full_function_name{const `member_type`&:`namespace`::member_name::extern_any-> - static_assert(full_function_name.find(splitter) != std::string_view::npos); - constexpr auto full_function_name_prefix_size = full_function_name.find(splitter) + splitter_size; - - // }>(void) noexcept - constexpr std::string_view suffix{"}>(void) noexcept"}; - constexpr auto full_function_name_suffix_size = suffix.size(); - #elif defined(GAL_PROMETHEUS_COMPILER_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) - // CLANG/CLANG-CL - // std::string_view `namespace`::get_full_function_name() [Vs = ::_Call(static_cast<(lambda at `ABS_FILE_PATH`\member_name.hpp:413:6) &>(_Obj), static_cast(_Arg1))){extern_any.`member_name`}>] - // constexpr auto full_function_name_size = full_function_name.size(); - - constexpr std::string_view splitter{"extern_any."}; - constexpr auto splitter_size = splitter.size(); - - // std::string_view `namespace`::get_full_function_name() [Vs = ::_Call(static_cast<(lambda at `ABS_FILE_PATH`\member_name.hpp:413:6) &>(_Obj), static_cast(_Arg1))){extern_any. - static_assert(full_function_name.find(splitter) != std::string_view::npos); - constexpr auto full_function_name_prefix_size = full_function_name.find(splitter) + splitter_size; - - // }>] - constexpr std::string_view suffix{"}>]"}; - constexpr auto full_function_name_suffix_size = suffix.size(); - #else - // GCC - // constexpr std::string_view `namespace`::get_full_function_name() [with auto ... = {`namespace`::member_name::wrapper{`namespace`::member_name::extern_any<`my_struct`>.`my_struct`::`member_name`}}; std::string_view = std::basic_string_view] - // constexpr auto full_function_name_size = full_function_name.size(); - - // fixme: find a suitable splitter. - // extern_any<`my_struct`>.`my_struct`::`member_name` - constexpr std::string_view type_name = name_of>(); - constexpr auto type_name_size = type_name.size() + 2; // 2 == `::` - constexpr std::string_view splitter{">."}; - constexpr auto splitter_size = splitter.size(); - - // constexpr std::string_view `namespace`::get_full_function_name() [with auto ... = {`namespace`::member_name::wrapper{`namespace`::member_name::extern_any<`my_struct`>.`my_struct`:: - static_assert(full_function_name.find(splitter) != std::string_view::npos); - constexpr auto full_function_name_prefix_size = full_function_name.find(splitter) + splitter_size + type_name_size; - - // }}; std::string_view = std::basic_string_view] - constexpr std::string_view suffix{"}}; std::string_view = std::basic_string_view]"}; - constexpr auto full_function_name_suffix_size = suffix.size(); - #endif - - auto name = full_function_name; - name.remove_prefix(full_function_name_prefix_size); - name.remove_suffix(full_function_name_suffix_size); - return name; - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END - - namespace member_detail - { - constexpr auto index_not_found = static_cast(-1); - - template - [[nodiscard]] constexpr auto index_of_member_name() noexcept -> std::size_t - { - return [](std::index_sequence) noexcept -> std::size_t - { - std::size_t index = index_not_found; - - ( - [&index]() noexcept // - { - if constexpr (name_of_member() == Name) { index = I; } - }. - template operator()(), - ...); - - return index; - }(std::make_index_sequence()>{}); - } - - template - [[nodiscard]] constexpr auto index_of_member_name(const std::string_view name) noexcept -> std::size_t - { - return [name](std::index_sequence) noexcept -> std::size_t - { - std::size_t index = index_not_found; - - ([&index, name]() noexcept // - { - if constexpr (name_of_member() == name) { index = I; } - }.template operator()(), - ...); - - return index; - }(std::make_index_sequence()>{}); - } - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - - template - requires member_gettable_t> - [[nodiscard]] constexpr auto has_member_of_name() noexcept -> bool // - { - return member_detail::index_of_member_name, Name>() != member_detail::index_not_found; - } - - template - requires member_gettable_t> - [[nodiscard]] constexpr auto has_member_of_name(const std::string_view name) noexcept -> bool // - { - return member_detail::index_of_member_name>(name) != member_detail::index_not_found; - } - - template - concept has_member_of_name_t = has_member_of_name(); - - template - requires has_member_of_name_t - [[nodiscard]] constexpr auto member_of_name(T&& value) noexcept -> decltype(auto) - { - return member_detail::visit( - [](Ts&&... args) noexcept -> decltype(auto) - { - constexpr auto index = member_detail::index_of_member_name(); - - return member_detail::nth_element(std::forward(args)...); - }, - std::forward(value)); - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -} diff --git a/src/meta/member.visit.inl b/src/meta/member.visit.inl new file mode 100644 index 00000000..0bbb77c7 --- /dev/null +++ b/src/meta/member.visit.inl @@ -0,0 +1,2893 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +namespace gal::prometheus::meta::member_detail +{ + template + [[nodiscard]] constexpr auto visit(Function&& function, T&& object) noexcept -> decltype(auto) + { + // std::forward + #define MEMBER_NAME_VISIT_DO_FORWARD(...) static_cast(__VA_ARGS__) + // forward like + #define MEMBER_NAME_VISIT_DO_FORWARD_LIKE(...) static_cast, std::add_lvalue_reference_t>, std::add_rvalue_reference_t>>>(__VA_ARGS__) + + #if __cpp_structured_bindings >= 202601L + auto&& [... vs] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke(MEMBER_NAME_VISIT_DO_FORWARD(function), MEMBER_NAME_VISIT_DO_FORWARD_LIKE(vs)...); + #else + if constexpr (constexpr auto size = member_size(); + size == 1) + { + auto&& [m0] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0) + ); + } + else if constexpr (size == 2) + { + auto&& [m0, m1] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1) + ); + } + else if constexpr (size == 3) + { + auto&& [m0, m1, m2] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2) + ); + } + else if constexpr (size == 4) + { + auto&& [m0, m1, m2, m3] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3) + ); + } + else if constexpr (size == 5) + { + auto&& [m0, m1, m2, m3, m4] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4) + ); + } + else if constexpr (size == 6) + { + auto&& [m0, m1, m2, m3, m4, m5] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5) + ); + } + else if constexpr (size == 7) + { + auto&& [m0, m1, m2, m3, m4, m5, m6] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6) + ); + } + else if constexpr (size == 8) + { + auto&& [m0, m1, m2, m3, m4, m5, m6, m7] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7) + ); + } + else if constexpr (size == 9) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8) + ); + } + else if constexpr (size == 10) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9) + ); + } + else if constexpr (size == 11) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10) + ); + } + else if constexpr (size == 12) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11) + ); + } + else if constexpr (size == 13) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12) + ); + } + else if constexpr (size == 14) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13) + ); + } + else if constexpr (size == 15) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13,m14 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14) + ); + } + else if constexpr (size == 16) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13,m14, m15 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15) + ); + } + else if constexpr (size == 17) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13,m14, m15, + m16 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16) + ); + } + else if constexpr (size == 18) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13,m14, m15, + m16, m17 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17) + ); + } + else if constexpr (size == 19) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13,m14, m15, + m16, m17, m18 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18) + ); + } + else if constexpr (size == 20) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13,m14, m15, + m16, m17, m18, m19 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19) + ); + } + else if constexpr (size == 21) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13,m14, m15, + m16, m17, m18, m19,m20 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20) + ); + } + else if constexpr (size == 22) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6,m7, + m8, m9, m10, m11, m12, m13,m14, m15, + m16, m17, m18, m19,m20, m21 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21) + ); + } + else if constexpr (size == 23) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13,m14, m15, + m16, m17, m18, m19,m20, m21, m22 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22) + ); + } + else if constexpr (size == 24) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13,m14, m15, + m16, m17, m18, m19,m20, m21, m22, m23 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23) + ); + } + else if constexpr (size == 25) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13,m14, m15, + m16, m17, m18, m19,m20, m21, m22, m23, + m24 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24) + ); + } + else if constexpr (size == 26) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6,m7, + m8, m9, m10, m11, m12, m13,m14, m15, + m16, m17, m18, m19,m20, m21, m22, m23, + m24, m25 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25) + ); + } + else if constexpr (size == 27) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13,m14, m15, + m16, m17, m18, m19,m20, m21, m22, m23, + m24, m25,m26 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26) + ); + } + else if constexpr (size == 28) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6,m7, + m8, m9, m10, m11, m12, m13,m14, m15, + m16, m17, m18, m19,m20, m21, m22, m23, + m24, m25,m26,m27 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27) + ); + } + else if constexpr (size == 29) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13,m14, m15, + m16, m17, m18, m19,m20, m21, m22, m23, + m24, m25,m26,m27, m28 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28) + ); + } + else if constexpr (size == 30) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6,m7, + m8, m9, m10, m11, m12, m13,m14, m15, + m16, m17, m18, m19,m20, m21, m22, m23, + m24, m25,m26,m27, m28, m29 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29) + ); + } + else if constexpr (size == 31) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, + m7, m8, m9, m10, m11, m12, m13,m14, m15, + m16, m17, m18, m19,m20, m21, m22, m23, + m24, m25,m26,m27, m28, m29, m30 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30) + ); + } + else if constexpr (size == 32) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13,m14, m15, + m16, m17, m18, m19,m20, m21, m22, m23, + m24, m25,m26,m27, m28, m29, m30, m31 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31) + ); + } + else if constexpr (size == 33) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6,m7, + m8, m9, m10, m11, m12, m13,m14, m15, + m16, m17, m18, m19,m20, m21, m22, m23, + m24, m25,m26,m27, m28, m29, m30, m31, + m32 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32) + ); + } + else if constexpr (size == 34) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6,m7, + m8, m9, m10, m11, m12,m13, m14, m15, + m16, m17, m18,m19, m20, m21, m22, m23, + m24,m25, m26,m27, m28, m29, m30, m31, + m32, m33 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33) + ); + } + else if constexpr (size == 35) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34) + ); + } + else if constexpr (size == 36) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35) + ); + } + else if constexpr (size == 37) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36) + ); + } + else if constexpr (size == 38) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37) + ); + } + else if constexpr (size == 39) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38) + ); + } + else if constexpr (size == 40) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39) + ); + } + else if constexpr (size == 41) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40) + ); + } + else if constexpr (size == 42) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, m8, + m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, + m31, m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41) + ); + } + else if constexpr (size == 43) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42) + ); + } + else if constexpr (size == 44) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43) + ); + } + else if constexpr (size == 45) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44) + ); + } + else if constexpr (size == 46) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45) + ); + } + else if constexpr (size == 47) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45, m46 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46) + ); + } + else if constexpr (size == 48) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45, m46, m47 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47) + ); + } + else if constexpr (size == 49) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45, m46, m47, + m48 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48) + ); + } + else if constexpr (size == 50) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45, m46, m47, + m48, m49 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49) + ); + } + else if constexpr (size == 51) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45, m46, m47, + m48, m49, m50 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50) + ); + } + else if constexpr (size == 52) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45, m46, m47, + m48, m49, m50,m51 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51) + ); + } + else if constexpr (size == 53) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45, m46, m47, + m48, m49, m50,m51, m52 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52) + ); + } + else if constexpr (size == 54) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45, m46, m47, + m48, m49, m50, m51, m52,m53 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53) + ); + } + else if constexpr (size == 55) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45, m46, m47, + m48, m49, m50, m51, m52,m53, m54 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54) + ); + } + else if constexpr (size == 56) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45, m46, m47, + m48, m49, m50, m51, m52,m53, m54, m55 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m55) + ); + } + else if constexpr (size == 57) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45, m46, m47, + m48, m49, m50, m51, m52,m53, m54, m55, + m56 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m55), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m56) + ); + } + else if constexpr (size == 58) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45, m46, m47, + m48, m49, m50, m51, m52,m53, m54, m55, + m56, m57 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m55), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m56), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m57) + ); + } + else if constexpr (size == 59) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45, m46, m47, + m48, m49, m50, m51, m52,m53, m54, m55, + m56, m57, m58 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m55), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m56), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m57), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m58) + ); + } + else if constexpr (size == 60) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45, m46, m47, + m48, m49, m50, m51, m52,m53, m54, m55, + m56, m57, m58, m59 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m55), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m56), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m57), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m58), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m59) + ); + } + else if constexpr (size == 61) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45, m46, m47, + m48, m49, m50, m51, m52,m53, m54, m55, + m56, m57, m58, m59, m60 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m55), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m56), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m57), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m58), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m59), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m60) + ); + } + else if constexpr (size == 62) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, m24, + m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45, m46, m47, + m48, m49, m50, m51, m52,m53, m54, m55, + m56, m57, m58, m59, m60, m61 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m55), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m56), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m57), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m58), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m59), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m60), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m61) + ); + } + else if constexpr (size == 63) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45, m46, m47, + m48, m49, m50, m51, m52,m53, m54, m55, + m56, m57, m58, m59, m60, m61, m62 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m55), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m56), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m57), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m58), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m59), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m60), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m61), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m62) + ); + } + else if constexpr (size == 64) + { + auto&& [ + m0, m1, m2, m3, m4, m5, m6, m7, + m8, m9, m10, m11, m12, m13, m14, m15, + m16, m17, m18, m19, m20, m21, m22, m23, + m24, m25, m26,m27, m28, m29, m30, m31, + m32, m33, m34, m35, m36, m37, m38, m39, + m40, m41, m42, m43, m44, m45, m46, m47, + m48, m49, m50, m51, m52,m53, m54, m55, + m56, m57, m58, m59, m60, m61, m62, m63 + ] = MEMBER_NAME_VISIT_DO_FORWARD(object); + return std::invoke( + MEMBER_NAME_VISIT_DO_FORWARD(function), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m0), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m1), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m2), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m3), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m4), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m5), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m6), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m7), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m8), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m9), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m10), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m11), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m12), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m13), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m14), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m15), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m16), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m17), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m18), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m19), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m20), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m21), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m22), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m23), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m24), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m25), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m26), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m27), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m28), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m29), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m30), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m31), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m32), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m33), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m34), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m35), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m36), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m37), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m38), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m39), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m40), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m41), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m42), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m43), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m44), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m45), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m46), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m47), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m48), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m49), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m50), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m51), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m52), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m53), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m54), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m55), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m56), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m57), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m58), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m59), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m60), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m61), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m62), + MEMBER_NAME_VISIT_DO_FORWARD_LIKE(m63) + ); + } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE("too much members."); } + #endif + + #undef MEMBER_NAME_VISIT_DO_FORWARD + #undef MEMBER_NAME_VISIT_DO_FORWARD_LIKE + } +} diff --git a/src/meta/meta.hpp b/src/meta/meta.hpp new file mode 100644 index 00000000..39dffe15 --- /dev/null +++ b/src/meta/meta.hpp @@ -0,0 +1,13 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include +#include +#include diff --git a/src/meta/meta.ixx b/src/meta/meta.ixx deleted file mode 100644 index 314aa409..00000000 --- a/src/meta/meta.ixx +++ /dev/null @@ -1,24 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -export module gal.prometheus.meta; - -export import :name; -export import :string; -export import :enumeration; -export import :member; -export import :to_string; - -#else -#pragma once - -#include -#include -#include -#include -#include - -#endif diff --git a/src/meta/name.hpp b/src/meta/name.hpp new file mode 100644 index 00000000..c80447f7 --- /dev/null +++ b/src/meta/name.hpp @@ -0,0 +1,99 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include + +#include + +struct gal_prometheus_meta_name_struct_123456789_987654321 {}; + +namespace gal::prometheus::meta +{ + namespace name_detail + { + template + struct unused_type {}; + + // template + // struct unused_value {}; + } + + template + [[nodiscard]] constexpr auto get_full_function_name() noexcept -> std::string_view + { + // [[maybe_unused]] constexpr name_detail::unused_type _{}; + std::ignore = name_detail::unused_type{}; + return std::source_location::current().function_name(); + } + + template + [[nodiscard]] constexpr auto get_full_function_name() noexcept -> std::string_view + { + // [[maybe_unused]] constexpr name_detail::unused_value _{}; + ((std::ignore = Vs), ...); + return std::source_location::current().function_name(); + } + + template + [[nodiscard]] constexpr auto name_of() noexcept -> std::string_view + { + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + // MSVC + // const class std::basic_string_view> `__calling_convention` `namespace`::get_full_function_name(void) + constexpr std::string_view full_function_name = get_full_function_name(); + constexpr auto full_function_name_size = full_function_name.size(); + + constexpr std::string_view dummy_struct_name{"struct gal_prometheus_meta_name_struct_123456789_987654321"}; + constexpr auto dummy_struct_name_size = std::ranges::size(dummy_struct_name); + + // const class std::basic_string_view> `__calling_convention` `namespace`::get_full_function_name< + constexpr auto full_function_name_prefix_size = full_function_name.find(dummy_struct_name); + static_assert(full_function_name_prefix_size != std::string_view::npos); + + // >(void) + constexpr auto full_function_name_suffix_size = full_function_name_size - full_function_name_prefix_size - dummy_struct_name_size; + #elif defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) + // CLANG/CLANG-CL + // std::string_view `namespace`::get_full_function_name() [T = `gal_prometheus_meta_name_struct_123456789_987654321`] + constexpr std::string_view full_function_name = get_full_function_name(); + constexpr auto full_function_name_size = full_function_name.size(); + + constexpr std::string_view dummy_struct_name{"gal_prometheus_meta_name_struct_123456789_987654321"}; + constexpr auto dummy_struct_name_size = std::ranges::size(dummy_struct_name); + + // std::string_view `namespace`::get_full_function_name() [T = + constexpr auto full_function_name_prefix_size = full_function_name.find(dummy_struct_name); + static_assert(full_function_name_prefix_size != std::string_view::npos); + + // ] + constexpr auto full_function_name_suffix_size = full_function_name_size - full_function_name_prefix_size - dummy_struct_name_size; + #else + // GCC + // constexpr std::string_view `namespace`::get_full_function_name() [with = `gal_prometheus_meta_name_struct_123456789_987654321`; std::string_view = std::basic_string_view] + constexpr std::string_view full_function_name = get_full_function_name(); + constexpr auto full_function_name_size = full_function_name.size(); + + constexpr std::string_view dummy_struct_name{"gal_prometheus_meta_name_struct_123456789_987654321"}; + constexpr auto dummy_struct_name_size = std::ranges::size(dummy_struct_name); + + // constexpr std::string_view `namespace`::get_full_function_name() [with = + constexpr auto full_function_name_prefix_size = full_function_name.find(dummy_struct_name); + static_assert(full_function_name_prefix_size != std::string_view::npos); + + // ; std::string_view = std::basic_string_view] + constexpr auto full_function_name_suffix_size = full_function_name_size - full_function_name_prefix_size - dummy_struct_name_size; + #endif + + auto full_name = get_full_function_name>(); + full_name.remove_prefix(full_function_name_prefix_size); + full_name.remove_suffix(full_function_name_suffix_size); + return full_name; + } +} diff --git a/src/meta/name.ixx b/src/meta/name.ixx deleted file mode 100644 index 833f3f64..00000000 --- a/src/meta/name.ixx +++ /dev/null @@ -1,95 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.meta:name; - -import std; - -#else -#pragma once - -#include -#include -#include -#include - -#include - -#endif - -struct dummy_struct_do_not_put_into_any_namespace {}; - -namespace gal::prometheus::meta -{ - namespace name_detail - { - // ReSharper disable once CppTemplateParameterNeverUsed - template // DO NOT REMOVE `Ts` - [[nodiscard]] constexpr auto get_full_function_name() noexcept -> std::string_view { return std::source_location::current().function_name(); } - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - template - [[nodiscard]] constexpr auto name_of() noexcept -> std::string_view - { - #if defined(GAL_PROMETHEUS_COMPILER_MSVC) - // MSVC - // const class std::basic_string_view> `__calling_convention` `namespace`::get_full_function_name(void) - constexpr std::string_view full_function_name = name_detail::get_full_function_name(); - constexpr auto full_function_name_size = full_function_name.size(); - - constexpr std::string_view dummy_struct_name{"struct dummy_struct_do_not_put_into_any_namespace"}; - constexpr auto dummy_struct_name_size = std::ranges::size(dummy_struct_name); - - // const class std::basic_string_view> `__calling_convention` `namespace`::get_full_function_name< - constexpr auto full_function_name_prefix_size = full_function_name.find(dummy_struct_name); - static_assert(full_function_name_prefix_size != std::string_view::npos); - - // >(void) - constexpr auto full_function_name_suffix_size = full_function_name_size - full_function_name_prefix_size - dummy_struct_name_size; - #elif defined(GAL_PROMETHEUS_COMPILER_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) - // CLANG/CLANG-CL - // std::string_view `namespace`::get_full_function_name() [T = `dummy_struct_do_not_put_into_any_namespace`] - constexpr std::string_view full_function_name = name_detail::get_full_function_name(); - constexpr auto full_function_name_size = full_function_name.size(); - - constexpr std::string_view dummy_struct_name{"dummy_struct_do_not_put_into_any_namespace"}; - constexpr auto dummy_struct_name_size = std::ranges::size(dummy_struct_name); - - // std::string_view `namespace`::get_full_function_name() [T = - constexpr auto full_function_name_prefix_size = full_function_name.find(dummy_struct_name); - static_assert(full_function_name_prefix_size != std::string_view::npos); - - // ] - constexpr auto full_function_name_suffix_size = full_function_name_size - full_function_name_prefix_size - dummy_struct_name_size; - #else - // GCC - // constexpr std::string_view `namespace`::get_full_function_name() [with = `dummy_struct_do_not_put_into_any_namespace`; std::string_view = std::basic_string_view] - constexpr std::string_view full_function_name = name_detail::get_full_function_name(); - constexpr auto full_function_name_size = full_function_name.size(); - - constexpr std::string_view dummy_struct_name{"dummy_struct_do_not_put_into_any_namespace"}; - constexpr auto dummy_struct_name_size = std::ranges::size(dummy_struct_name); - - // constexpr std::string_view `namespace`::get_full_function_name() [with = - constexpr auto full_function_name_prefix_size = full_function_name.find(dummy_struct_name); - static_assert(full_function_name_prefix_size != std::string_view::npos); - - // ; std::string_view = std::basic_string_view] - constexpr auto full_function_name_suffix_size = full_function_name_size - full_function_name_prefix_size - dummy_struct_name_size; - #endif - - auto full_name = name_detail::get_full_function_name>(); - full_name.remove_prefix(full_function_name_prefix_size); - full_name.remove_suffix(full_function_name_suffix_size); - return full_name; - } - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -} diff --git a/src/meta/string.hpp b/src/meta/string.hpp new file mode 100644 index 00000000..40a3264a --- /dev/null +++ b/src/meta/string.hpp @@ -0,0 +1,789 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace gal::prometheus::meta +{ + /** + * @brief An immutable array of fixed-length characters. + */ + template + struct basic_char_array; + + /** + * @brief A mutable array of fixed-length characters. + */ + template + struct basic_fixed_string; + + template + using basic_string_view = std::basic_string_view; + + namespace string_detail + { + template + struct is_basic_char_array : std::false_type {}; + + template + struct is_basic_char_array> : std::true_type {}; + + template + constexpr auto is_basic_char_array_v = is_basic_char_array::value; + + template + struct is_basic_fixed_string : std::false_type {}; + + template + struct is_basic_fixed_string> : std::true_type {}; + + template + constexpr auto is_basic_fixed_string_v = is_basic_fixed_string::value; + + template + concept getter_t = requires(const Container& container, Getter&& getter) + { + std::invoke( + std::forward(getter), + container, + std::declval() + ); + }; + + template + concept comparator_t = requires(const Container& container, Getter&& getter, Comparator&& comparator) + { + std::invoke( + std::forward(comparator), + std::invoke( + std::forward(getter), + container, + std::declval() + ), + std::declval() + ); + }; + + template + requires requires(const Container& container, const SizeType index) { container[index]; } + struct default_getter + { + [[nodiscard]] constexpr auto operator()(Container& container, const SizeType index) noexcept(noexcept(container[index])) -> decltype(auto) + { + return container[index]; + } + + [[nodiscard]] constexpr auto operator()(const Container& container, const SizeType index) const noexcept(noexcept(container[index])) -> decltype(auto) + { + return container[index]; + } + }; + + template + struct default_comparator + { + using const_reference = std::add_const_t>>; + + [[nodiscard]] constexpr auto operator()(const_reference left, const_reference right) const noexcept(noexcept(left == right)) + { + return left == right; + } + }; + + enum class MetaStringDerivedCategory : std::uint8_t + { + MEMBER, + MEMBER_FUNCTION, + STATIC, + STATIC_FUNCTION, + }; + + /** + * std::is_member_object_pointer_v and `not` std::is_member_function_pointer_v -> member-data + * `not` std::is_member_object_pointer_v and std::is_member_function_pointer_v -> member-function + * + * `not` std::is_member_object_pointer_v and `not` std::is_member_function_pointer_v -> static-data + * `not` std::is_member_object_pointer_v and `not` std::is_member_function_pointer_v -> static-function(invocable) + */ + template + struct pointer_traits; + + // member data + template + requires( + std::is_member_object_pointer_v and + not std::is_member_function_pointer_v + ) and + std::is_convertible_v().value), std::add_pointer_t().value)>>>>> + struct pointer_traits + { + // const pointer + using type = std::add_pointer_t().value)>>>>; + + constexpr static auto value = MetaStringDerivedCategory::MEMBER; + }; + + // member function + template + requires( + not std::is_member_object_pointer_v and + std::is_member_function_pointer_v + ) and requires(const T& derived) + { + { + derived.data() + } -> std::convertible_to>; + } + struct pointer_traits + { + // const pointer + using type = std::add_pointer_t().data())>>>>; + + constexpr static auto value = MetaStringDerivedCategory::MEMBER_FUNCTION; + }; + + // static data + template + requires( + not std::is_member_object_pointer_v and + not std::is_member_function_pointer_v + ) and (not std::is_invocable_v) + struct pointer_traits + { + // const pointer + using type = std::add_pointer_t>>>; + + constexpr static auto value = MetaStringDerivedCategory::STATIC; + }; + + // static function + template + requires( + not std::is_member_object_pointer_v and + not std::is_member_function_pointer_v + ) and (std::is_invocable_v) + struct pointer_traits + { + // const pointer + using type = std::add_pointer_t>>>; + + constexpr static auto value = MetaStringDerivedCategory::STATIC_FUNCTION; + }; + + template + struct lazy_pointer_traits + { + using pointer = typename pointer_traits::type; + + constexpr static auto category = pointer_traits::value; + constexpr static auto is_static = (category == MetaStringDerivedCategory::STATIC) or (category == MetaStringDerivedCategory::STATIC_FUNCTION); + }; + + template + [[nodiscard]] consteval auto lazy_is_static() noexcept -> bool + { + using traits_type = lazy_pointer_traits; + + return traits_type::is_static; + } + + template + [[nodiscard]] consteval auto lazy_is_pointer() noexcept -> bool + { + using traits_type = lazy_pointer_traits; + + return std::is_same_v; + } + + template + struct meta_string_base; + + template + struct is_meta_string_base : std::false_type {}; + + template + struct is_meta_string_base> : std::true_type {}; + + template + requires requires { typename T::base_type; } + struct is_meta_string_base : is_meta_string_base {}; + + template + constexpr auto is_meta_string_base_v = is_meta_string_base::value; + + template + concept meta_string_base_t = is_meta_string_base_v; + + template + struct meta_string_base + { + using derived_type = std::remove_cvref_t; + + using value_type = ValueType; + using size_type = SizeType; + + private: + [[nodiscard]] constexpr auto rep_value() const noexcept -> auto // + requires(not lazy_is_static()) + { + if constexpr (lazy_pointer_traits::category == MetaStringDerivedCategory::MEMBER) + { + return static_cast::pointer>(static_cast(*this).value); + } + else if constexpr (lazy_pointer_traits::category == MetaStringDerivedCategory::MEMBER_FUNCTION) + { + return static_cast::pointer>(static_cast(*this).data()); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + [[nodiscard]] constexpr static auto rep_value() noexcept -> auto // + requires(lazy_is_static()) + { + if constexpr (lazy_pointer_traits::category == MetaStringDerivedCategory::STATIC) + { + return static_cast::pointer>(derived_type::value); + } + else if constexpr (lazy_pointer_traits::category == MetaStringDerivedCategory::STATIC_FUNCTION) + { + return static_cast::pointer>(derived_type::data()); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + [[nodiscard]] constexpr auto rep_size() const noexcept -> size_type // + requires(not lazy_is_static()) + { + if constexpr (lazy_pointer_traits::category == MetaStringDerivedCategory::MEMBER) + { + return static_cast(*this).size; + } + else if constexpr (lazy_pointer_traits::category == MetaStringDerivedCategory::MEMBER_FUNCTION) + { + return static_cast(*this).size(); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + [[nodiscard]] constexpr static auto rep_size() noexcept -> size_type // + requires(lazy_is_static()) + { + if constexpr (lazy_pointer_traits::category == MetaStringDerivedCategory::STATIC) + { + return derived_type::size; + } + else if constexpr (lazy_pointer_traits::category == MetaStringDerivedCategory::STATIC_FUNCTION) + { + return derived_type::size(); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + template + constexpr static auto is_constructible_from_data_v = std::is_constructible_v::pointer, size_type>; + template + constexpr static auto is_constructible_from_view_v = std::is_constructible_v>; + + public: + // ================================================= + + template + requires(is_constructible_from_data_v) + [[nodiscard]] constexpr explicit operator StringType() const noexcept // + requires(not lazy_is_static()) + { + return StringType{rep_value(), rep_size()}; + } + + template + requires(is_constructible_from_view_v and not is_constructible_from_data_v) + [[nodiscard]] constexpr explicit(not std::is_convertible_v, StringType>) operator StringType() const noexcept // + requires(not lazy_is_static()) + { + return StringType{basic_string_view{rep_value(), rep_size()}}; + } + + template + requires(is_constructible_from_data_v) + [[nodiscard]] constexpr auto as() const noexcept -> StringType // + requires(not lazy_is_static()) + { + return StringType{this->operator basic_string_view()}; + } + + template + requires(is_constructible_from_data_v) + [[nodiscard]] constexpr static auto as() noexcept -> StringType // + requires(lazy_is_static()) + { + return StringType{meta_string_base::rep_value(), meta_string_base::rep_size()}; + } + + // ================================================= + + template + [[nodiscard]] friend constexpr auto operator==(const derived_type& lhs, Pointer string) noexcept -> bool // + requires(lazy_is_pointer()) // + { + return std::char_traits::length(string) == lhs.rep_size() and std::char_traits::compare(string, lhs.rep_value(), lhs.rep_size()) == 0; + } + + template + [[nodiscard]] constexpr auto match(Pointer string) const noexcept -> bool // + requires(lazy_is_pointer() and not lazy_is_static()) // + { + return *this == string; + } + + template + [[nodiscard]] constexpr static auto match(Pointer string) noexcept -> bool // + requires(lazy_is_pointer() and lazy_is_static()) // + { + return std::char_traits::length(string) == rep_size() and std::char_traits::compare(string, rep_value(), rep_size()) == 0; + } + + template + [[nodiscard]] friend constexpr auto operator==(const derived_type& lhs, const Rhs& rhs) noexcept -> bool // + { + return lhs.template as>() == rhs.template as>(); + } + + template + requires(not lazy_is_static()) + [[nodiscard]] constexpr auto match(const String& rhs) const noexcept -> bool // + { + return this->template as>() == rhs.template as>(); + } + + template + requires(lazy_is_static()) + [[nodiscard]] constexpr static auto match(const String& rhs) noexcept -> bool // + { + return meta_string_base::as>() == rhs.template as>(); + } + + template + requires(std::is_constructible_v, String> and not is_meta_string_base_v) + [[nodiscard]] friend constexpr auto operator==(const derived_type& lhs, const String& rhs) noexcept(std::is_nothrow_constructible_v, String>) -> bool // + { + // + return lhs.template as>() == basic_string_view{rhs}; + } + + template + requires(std::is_constructible_v, String> and not is_meta_string_base_v) + [[nodiscard]] constexpr auto match(const String& rhs) const noexcept(std::is_nothrow_constructible_v, String>) -> bool // + requires(not lazy_is_static()) + { + // + return *this == rhs; + } + + template + requires(std::is_constructible_v, String> and not is_meta_string_base_v) + [[nodiscard]] constexpr static auto match(const String& rhs) noexcept(std::is_nothrow_constructible_v, String>) -> bool // + requires(lazy_is_static()) + { + // + return meta_string_base::as>() == basic_string_view{rhs}; + } + + // container + getter + comparator + template + requires getter_t and comparator_t + [[nodiscard]] constexpr auto + match( + const Container& container, + Getter getter, + Comparator comparator // + ) const noexcept(noexcept(comparator(getter(container, 0), rep_value()[0]))) -> bool // + requires(not lazy_is_static()) + { + if (std::ranges::equal(std::ranges::size(container), rep_size())) + { + return false; + } + + return std::ranges::all_of( + std::views::iota(static_cast(0), rep_size()), + [comparator](const auto& pair) noexcept(noexcept(comparator(pair.first, pair.second))) { return comparator(pair.first, pair.second); }, + [this, &container, getter](const auto index) noexcept(noexcept(getter(container, index))) + { + return std::make_pair(std::cref(getter(container, index)), std::cref(rep_value()[index])); + } + ); + } + + // container + getter + comparator + template + requires getter_t and comparator_t + [[nodiscard]] constexpr static auto + match( + const Container& container, + Getter getter, + Comparator comparator // + ) noexcept(noexcept(comparator(getter(container, 0), rep_value()[0]))) -> bool // + requires(lazy_is_static()) + { + if (std::ranges::equal(std::ranges::size(container), rep_size())) + { + return false; + } + + return std::ranges::all_of( + std::views::iota(static_cast(0), rep_size()), + [comparator](const auto& pair) noexcept(noexcept(comparator(pair.first, pair.second))) { return comparator(pair.first, pair.second); }, + [&container, getter](const auto index) noexcept(noexcept(getter(container, index))) + { + return std::make_pair(std::cref(getter(container, index)), std::cref(rep_value()[index])); + } + ); + } + + // container + default_getter + comparator + template + requires comparator_t, Comparator, size_type, value_type> + [[nodiscard]] constexpr auto + match( + const Container& container, + Comparator comparator // + ) const noexcept(noexcept(comparator(default_getter{}(container, 0), rep_value()[0]))) -> bool // + requires(not lazy_is_static()) + { + // + return this->template match, Comparator>(container, default_getter{}, comparator); + } + + // container + default_getter + comparator + template + requires comparator_t, Comparator, size_type, value_type> + [[nodiscard]] constexpr static auto + match( + const Container& container, + Comparator comparator // + ) noexcept(noexcept(comparator(default_getter{}(container, 0), rep_value()[0]))) -> bool // + requires(lazy_is_static()) + { + // + return meta_string_base::match, Comparator>(container, default_getter{}, comparator); + } + + // container + getter + default_comparator + template + requires getter_t + [[nodiscard]] constexpr auto + match( + const Container& container, + Getter getter // + ) const noexcept(noexcept(default_comparator{}(getter(container, 0), rep_value()[0]))) -> bool // + requires(not lazy_is_static()) + { + // + return this->template match>(container, getter, default_comparator{}); + } + + // container + getter + default_comparator + template + requires getter_t + [[nodiscard]] constexpr static auto + match( + const Container& container, + Getter getter // + ) noexcept(noexcept(default_comparator{}(getter(container, 0), rep_value()[0]))) -> bool // + requires(lazy_is_static()) + { + // + return meta_string_base::match>(container, getter, default_comparator{}); + } + + // container + default_getter + default_comparator + template + requires getter_t + [[nodiscard]] constexpr auto match( + const Container& container // + ) const noexcept(noexcept(default_comparator{ + }(default_getter{}(container, 0), + rep_value()[0]))) -> bool // + requires(not lazy_is_static()) + { + // + return this->template match>( + container, + default_getter{}, + default_comparator{} + ); + } + + // container + default_getter + default_comparator + template + requires getter_t + [[nodiscard]] constexpr static auto match( + const Container& container // + ) noexcept(noexcept(default_comparator{ + }(default_getter{}(container, 0), + rep_value()[0]))) -> bool // + requires(lazy_is_static()) + { + // + return meta_string_base::match>( + container, + default_getter{}, + default_comparator{} + ); + } + }; + + template + [[nodiscard]] consteval auto contains_zero() noexcept -> bool + { + if constexpr (sizeof...(Cs) == 0) + { + return This == static_cast(0); + } + else + { + return contains_zero(); + } + } + } // namespace string_detail + + template + concept basic_char_array_t = string_detail::is_basic_char_array_v; + + template + concept basic_fixed_string_t = string_detail::is_basic_fixed_string_v; + + template + struct basic_char_array : string_detail::meta_string_base, T, std::size_t> + { + using base_type = string_detail::meta_string_base; + + using value_type = T; + using size_type = std::size_t; + + using const_pointer = const value_type*; + + constexpr static size_type max_size = sizeof...(Cs); + constexpr static value_type value[max_size]{Cs...}; + constexpr static size_type size = max_size - (value[max_size - 1] == '\0'); + + [[nodiscard]] constexpr static const_pointer begin() noexcept { return std::ranges::begin(value); } + + [[nodiscard]] constexpr static const_pointer end() noexcept { return std::ranges::end(value); } + + template + requires(N <= max_size) + [[nodiscard]] constexpr static auto as_fixed_string() noexcept + { + return basic_fixed_string{basic_string_view{value, N}}; + } + + // // basic_char_array <=> basic_char_array + // template + // [[nodiscard]] constexpr auto operator<=>(const basic_char_array& rhs) const noexcept -> auto + // { + // // + // return this->template as>() <=> rhs.template as>(); + // } + // + // // basic_char_array <=> string + // template + // requires(not basic_char_array_t) and std::is_constructible_v, String> + // [[nodiscard]] constexpr auto operator<=>(const String& rhs) const noexcept(std::is_nothrow_constructible_v, String>) -> auto + // { + // // + // return this->template as>() <=> basic_string_view{rhs}; + // } + // + // // string <=> basic_char_array + // template + // requires(not basic_char_array_t) and std::is_constructible_v, String> + // [[nodiscard]] friend constexpr auto operator<=>(const String& lhs, const basic_char_array& rhs) noexcept(std::is_nothrow_constructible_v, String>) -> auto + // { + // // + // return basic_string_view{lhs} <=> rhs.template as>(); + // } + }; + + template + [[nodiscard]] constexpr auto to_char_array() noexcept -> auto + { + using fixed_string_type = decltype(FixedString); + + return [](const std::index_sequence) noexcept + { + return basic_char_array{}; + }(std::make_index_sequence{}); + } + + template + using char_array = basic_char_array; + template + using wchar_array = basic_char_array; + template + // ReSharper disable once CppInconsistentNaming + using u8char_array = basic_char_array; + template + // ReSharper disable once CppInconsistentNaming + using u16char_array = basic_char_array; + template + // ReSharper disable once CppInconsistentNaming + using u32char_array = basic_char_array; + + template + struct basic_fixed_string : string_detail::meta_string_base, T, std::size_t> + { + using base_type = string_detail::meta_string_base; + + using value_type = T; + using size_type = std::size_t; + + using pointer = value_type*; + using const_pointer = const value_type*; + + constexpr static size_type max_size{N}; + constexpr static size_type size{max_size - 1}; + value_type value[max_size]{}; + + constexpr basic_fixed_string() noexcept = default; + + template + requires(M >= N) + constexpr explicit(false) basic_fixed_string(const value_type (&string)[M]) noexcept + { + std::ranges::copy(std::ranges::begin(string), std::ranges::begin(string) + size, value); + } + + template + requires( + // DO NOT USE `basic_char_array::size`! + sizeof...(Cs) - ((Cs == 0) or ...) >= N) + constexpr explicit(false) basic_fixed_string(const basic_char_array& char_array) noexcept + { + std::ranges::copy(std::ranges::begin(char_array), std::ranges::begin(char_array) + N, value); + } + + template + requires std::is_same_v, value_type> + constexpr explicit basic_fixed_string(const String& string) noexcept + { + std::ranges::copy(std::ranges::begin(string), std::ranges::begin(string) + N, value); + } + + [[nodiscard]] constexpr auto begin() noexcept -> pointer { return value; } + + [[nodiscard]] constexpr auto begin() const noexcept -> const_pointer { return value; } + + [[nodiscard]] constexpr auto end() noexcept -> pointer { return value + size; } + + [[nodiscard]] constexpr auto end() const noexcept -> const_pointer { return value + size; } + + // // basic_fixed_string <=> basic_fixed_string + // template + // [[nodiscard]] constexpr auto operator<=>(const basic_fixed_string& rhs) const noexcept -> auto + // { + // // + // return this->template as>() <=> rhs.template as>(); + // } + // + // // basic_fixed_string <=> string + // template + // requires(not basic_fixed_string_t) and std::is_constructible_v, String> + // [[nodiscard]] constexpr auto operator<=>(const String& rhs) const noexcept(std::is_nothrow_constructible_v, String>) -> auto + // { + // // + // return this->template as>() <=> basic_string_view{rhs}; + // } + // + // // string <=> basic_fixed_string + // template + // requires(not basic_fixed_string_t) and std::is_constructible_v, String> + // [[nodiscard]] friend constexpr auto operator<=>(const String& lhs, const basic_fixed_string& rhs) noexcept(std::is_nothrow_constructible_v, String>) -> auto + // { + // // + // return basic_string_view{lhs} <=> rhs.template as>(); + // } + }; + + template + basic_fixed_string(const T (&string)[N]) -> basic_fixed_string; + + template + basic_fixed_string(basic_char_array char_array) -> basic_fixed_string< + T, + // DO NOT USE `basic_char_array::size`! + sizeof...(Cs) - string_detail::contains_zero() + >; + + template + using fixed_string = basic_fixed_string; + template + // ReSharper disable once IdentifierTypo + using fixed_wstring = basic_fixed_string; + template + // ReSharper disable once CppInconsistentNaming + using fixed_u8string = basic_fixed_string; + template + // ReSharper disable once CppInconsistentNaming + using fixed_u16string = basic_fixed_string; + template + // ReSharper disable once CppInconsistentNaming + using fixed_u32string = basic_fixed_string; +} // namespace gal::prometheus::meta + +namespace std +{ + // for `std::totally_ordered_with` + template typename Q1, template typename Q2> + requires std::is_constructible_v, String> + struct basic_common_reference // NOLINT(cert-dcl58-cpp) + { + using type = gal::prometheus::meta::basic_string_view; + }; + + template typename Q1, template typename Q2> + requires std::is_constructible_v, String> + struct basic_common_reference // NOLINT(cert-dcl58-cpp) + { + using type = gal::prometheus::meta::basic_string_view; + }; + + // for `std::totally_ordered_with` + template typename Q1, template typename Q2> + requires std::is_constructible_v, String> + struct basic_common_reference // NOLINT(cert-dcl58-cpp) + { + using type = gal::prometheus::meta::basic_string_view; + }; + + template typename Q1, template typename Q2> + requires std::is_constructible_v, String> + struct basic_common_reference // NOLINT(cert-dcl58-cpp) + { + using type = gal::prometheus::meta::basic_string_view; + }; +} diff --git a/src/meta/string.ixx b/src/meta/string.ixx deleted file mode 100644 index 28ecbe58..00000000 --- a/src/meta/string.ixx +++ /dev/null @@ -1,798 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.meta:string; - -import std; - -#else -#pragma once - -#include -#include -#include -#include -#include -#include - -#include - -#endif - -namespace gal::prometheus::meta -{ - namespace string_detail - { - template - concept getter_t = requires(const Container& container, Getter&& getter) - { - std::invoke(std::forward(getter), container, std::declval()); - }; - - template< - typename Container, - typename Getter, - typename Comparator, - typename SizeType = typename Container::size_type, - typename ValueType =typename Container::value_type> - concept comparator_t = requires(const Container& container, Getter&& getter, Comparator&& comparator) - { - std::invoke( - std::forward(comparator), - std::invoke(std::forward(getter), container, std::declval()), - std::declval() - ); - }; - - template - requires requires(const Container& container, const SizeType index) - { - container[index]; - } - struct default_getter - { - [[nodiscard]] constexpr auto operator()(Container& container, const SizeType index) noexcept(noexcept(container[index])) -> decltype(auto) - { - return container[index]; - } - - [[nodiscard]] constexpr auto operator()( - const Container& container, - const SizeType index - ) const noexcept(noexcept(container[index])) -> decltype(auto) { return container[index]; } - }; - - template - struct default_comparator - { - using const_reference = std::add_const_t>>; - - [[nodiscard]] constexpr auto operator()(const_reference left, const_reference right) const noexcept(noexcept(left == right)) - { - return left == right; - } - }; - - template - using default_view = std::basic_string_view; - - enum class MetaStringDerivedCategory - { - MEMBER, - MEMBER_FUNCTION, - STATIC, - STATIC_FUNCTION, - }; - - /** - * std::is_member_object_pointer_v and `not` std::is_member_function_pointer_v -> member-data - * `not` std::is_member_object_pointer_v and std::is_member_function_pointer_v -> member-function - * - * `not` std::is_member_object_pointer_v and `not` std::is_member_function_pointer_v -> static-data - * `not` std::is_member_object_pointer_v and `not` std::is_member_function_pointer_v -> static-function(invocable) - */ - template - struct pointer_traits; - - // member data - template - requires - (std::is_member_object_pointer_v and not std::is_member_function_pointer_v) and - std::is_convertible_v< - decltype(std::declval().value), - std::add_pointer_t().value)>>>> - > - struct pointer_traits - { - // const pointer - using type = std::add_pointer_t().value)>>>>; - constexpr static auto value = MetaStringDerivedCategory::MEMBER; - }; - - // member function - template - requires(not std::is_member_object_pointer_v and std::is_member_function_pointer_v) and - requires(const T& derived) - { - { - derived.data() - } -> std::convertible_to>; - } - struct pointer_traits - { - // const pointer - using type = std::add_pointer_t().data())>>>>; - constexpr static auto value = MetaStringDerivedCategory::MEMBER_FUNCTION; - }; - - // static data - template - requires (not std::is_member_object_pointer_v and not std::is_member_function_pointer_v) and - (not std::is_invocable_v) - struct pointer_traits - { - // const pointer - using type = std::add_pointer_t>>>; - constexpr static auto value = MetaStringDerivedCategory::STATIC; - }; - - // static function - template - requires(not std::is_member_object_pointer_v and not std::is_member_function_pointer_v) and - (std::is_invocable_v) - struct pointer_traits - { - // const pointer - using type = std::add_pointer_t>>>; - constexpr static auto value = MetaStringDerivedCategory::STATIC_FUNCTION; - }; - - template - struct lazy_pointer_traits - { - using pointer = typename pointer_traits::type; - - constexpr static auto category = pointer_traits::value; - constexpr static auto is_static = - (category == MetaStringDerivedCategory::STATIC) or - (category == MetaStringDerivedCategory::STATIC_FUNCTION); - }; - - template - [[nodiscard]] consteval auto lazy_is_static() noexcept -> bool - { - using traits_type = lazy_pointer_traits; - - return traits_type::is_static; - } - - template - [[nodiscard]] consteval auto lazy_is_pointer() noexcept -> bool - { - using traits_type = lazy_pointer_traits; - - return std::is_same_v; - } - - template - struct meta_string_base; - - template - struct is_meta_string_base : std::false_type {}; - - template - struct is_meta_string_base> : std::true_type {}; - - template - requires requires { typename T::base_type; } - struct is_meta_string_base : is_meta_string_base {}; - - template - constexpr auto is_meta_string_base_v = is_meta_string_base::value; - template - concept meta_string_base_t = is_meta_string_base_v; - - template - struct meta_string_base - { - using derived_type = std::remove_cvref_t; - - using value_type = ValueType; - using size_type = SizeType; - - private: - [[nodiscard]] constexpr auto rep_value() const noexcept -> auto // - requires(not lazy_is_static()) - { - if constexpr (lazy_pointer_traits::category == MetaStringDerivedCategory::MEMBER) - { - return static_cast::pointer>(static_cast(*this).value); - } - else if constexpr (lazy_pointer_traits::category == MetaStringDerivedCategory::MEMBER_FUNCTION) - { - return static_cast::pointer>(static_cast(*this).data()); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - [[nodiscard]] constexpr static auto rep_value() noexcept -> auto // - requires(lazy_is_static()) - { - if constexpr (lazy_pointer_traits::category == MetaStringDerivedCategory::STATIC) - { - return static_cast::pointer>(derived_type::value); - } - else if constexpr (lazy_pointer_traits::category == MetaStringDerivedCategory::STATIC_FUNCTION) - { - return static_cast::pointer>(derived_type::data()); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - [[nodiscard]] constexpr auto rep_size() const noexcept -> size_type // - requires(not lazy_is_static()) - { - if constexpr (lazy_pointer_traits::category == MetaStringDerivedCategory::MEMBER) - { - return static_cast(*this).size; - } - else if constexpr (lazy_pointer_traits::category == MetaStringDerivedCategory::MEMBER_FUNCTION) - { - return static_cast(*this).size(); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - [[nodiscard]] constexpr static auto rep_size() noexcept -> size_type // - requires(lazy_is_static()) - { - if constexpr (lazy_pointer_traits::category == MetaStringDerivedCategory::STATIC) { return derived_type::size; } - else if constexpr (lazy_pointer_traits::category == MetaStringDerivedCategory::STATIC_FUNCTION) - { - return derived_type::size(); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template - constexpr static auto is_constructible_from_data_v = std::is_constructible_v< - StringType, typename lazy_pointer_traits::pointer, size_type - >; - template - constexpr static auto is_constructible_from_view_v = std::is_constructible_v>; - - public: - // ================================================= - - template - requires is_constructible_from_data_v - [[nodiscard]] constexpr explicit operator StringType() const noexcept // - requires(not lazy_is_static()) { return StringType{rep_value(), rep_size()}; } - - template - requires is_constructible_from_view_v and (not is_constructible_from_data_v) - [[nodiscard]] constexpr explicit(not std::is_convertible_v, StringType>) operator StringType() const noexcept // - requires(not lazy_is_static()) { return StringType{default_view{rep_value(), rep_size()}}; } - - template - requires is_constructible_from_data_v - [[nodiscard]] constexpr auto as() const noexcept -> StringType // - requires(not lazy_is_static()) { return StringType{this->operator default_view()}; } - - template - requires is_constructible_from_data_v - [[nodiscard]] constexpr static auto as() noexcept -> StringType // - requires (lazy_is_static()) { return StringType{meta_string_base::rep_value(), meta_string_base::rep_size()}; } - - // ================================================= - - template - [[nodiscard]] friend constexpr auto operator==(const derived_type& lhs, Pointer string) noexcept -> bool // - requires (lazy_is_pointer()) // - { - return std::char_traits::length(string) == lhs.rep_size() and - std::char_traits::compare( - string, - lhs.rep_value(), - lhs.rep_size() - ) == 0; - } - - template - [[nodiscard]] constexpr auto match(Pointer string) const noexcept -> bool // - requires(lazy_is_pointer() and not lazy_is_static()) // - { - return *this == string; - } - - template - [[nodiscard]] constexpr static auto match(Pointer string) noexcept -> bool // - requires(lazy_is_pointer() and lazy_is_static()) // - { - return std::char_traits::length(string) == rep_size() and - std::char_traits::compare( - string, - rep_value(), - rep_size() - ) == 0; - } - - template - [[nodiscard]] friend constexpr auto operator==(const derived_type& lhs, const Rhs& rhs) noexcept -> bool // - { - return lhs.template as>() == rhs.template as>(); - } - - template - requires(not lazy_is_static()) - [[nodiscard]] constexpr auto match(const String& rhs) const noexcept -> bool // - { - return this->template as>() == rhs.template as>(); - } - - template - requires(lazy_is_static()) - [[nodiscard]] constexpr static auto match(const String& rhs) noexcept -> bool // - { - return meta_string_base::as>() == rhs.template as>(); - } - - template - requires (std::is_constructible_v, String> and not is_meta_string_base_v) - [[nodiscard]] friend constexpr auto operator==( - const derived_type& lhs, - const String& rhs - ) noexcept(std::is_nothrow_constructible_v, String>) -> bool // - { - // - return lhs.template as>() == default_view{rhs}; - } - - template - requires(std::is_constructible_v, String> and not is_meta_string_base_v) - [[nodiscard]] constexpr auto match(const String& rhs) const - noexcept(std::is_nothrow_constructible_v, String>) -> bool // - requires(not lazy_is_static()) - { - // - return *this == rhs; - } - - template - requires(std::is_constructible_v, String> and not is_meta_string_base_v) - [[nodiscard]] constexpr static auto match(const String& rhs) - noexcept(std::is_nothrow_constructible_v, String>) -> bool // - requires(lazy_is_static()) - { - // - return meta_string_base::as>() == default_view{rhs}; - } - - // container + getter + comparator - template - requires getter_t and comparator_t - [[nodiscard]] constexpr auto match( - const Container& container, - Getter getter, - Comparator comparator // - ) const noexcept(noexcept(comparator(getter(container, 0), rep_value()[0]))) -> bool // - requires(not lazy_is_static()) - { - if (std::ranges::equal(std::ranges::size(container), rep_size())) { return false; } - - return std::ranges::all_of( - std::views::iota(static_cast(0), rep_size()), - [comparator](const auto& pair) noexcept(noexcept(comparator(pair.first, pair.second))) - { - return comparator(pair.first, pair.second); - }, - [this, &container, getter](const auto index) noexcept(noexcept(getter(container, index))) - { - return std::make_pair(std::cref(getter(container, index)), std::cref(rep_value()[index])); - }); - } - - // container + getter + comparator - template - requires getter_t and comparator_t - [[nodiscard]] constexpr static auto match( - const Container& container, - Getter getter, - Comparator comparator // - ) noexcept(noexcept(comparator(getter(container, 0), rep_value()[0]))) -> bool // - requires(lazy_is_static()) - { - if (std::ranges::equal(std::ranges::size(container), rep_size())) { return false; } - - return std::ranges::all_of( - std::views::iota(static_cast(0), rep_size()), - [comparator](const auto& pair) noexcept(noexcept(comparator(pair.first, pair.second))) - { - return comparator(pair.first, pair.second); - }, - [&container, getter](const auto index) noexcept(noexcept(getter(container, index))) - { - return std::make_pair(std::cref(getter(container, index)), std::cref(rep_value()[index])); - }); - } - - // container + default_getter + comparator - template - requires comparator_t, Comparator, size_type, value_type> - [[nodiscard]] constexpr auto match( - const Container& container, - Comparator comparator // - ) const noexcept(noexcept(comparator(default_getter{}(container, 0), rep_value()[0]))) -> bool // - requires(not lazy_is_static()) - { - // - return this->template match, Comparator>( - container, - default_getter{}, - comparator); - } - - // container + default_getter + comparator - template - requires comparator_t, Comparator, size_type, value_type> - [[nodiscard]] constexpr static auto match( - const Container& container, - Comparator comparator // - ) noexcept(noexcept(comparator(default_getter{}(container, 0), rep_value()[0]))) -> bool // - requires(lazy_is_static()) - { - // - return meta_string_base::match, Comparator>( - container, - default_getter{}, - comparator); - } - - // container + getter + default_comparator - template - requires getter_t - [[nodiscard]] constexpr auto match( - const Container& container, - Getter getter // - ) const noexcept(noexcept(default_comparator{}(getter(container, 0), rep_value()[0]))) -> bool // - requires(not lazy_is_static()) - { - // - return this->template match>( - container, - getter, - default_comparator{}); - } - - // container + getter + default_comparator - template - requires getter_t - [[nodiscard]] constexpr static auto match( - const Container& container, - Getter getter // - ) noexcept(noexcept(default_comparator{}(getter(container, 0), rep_value()[0]))) -> bool // - requires(lazy_is_static()) - { - // - return meta_string_base::match>( - container, - getter, - default_comparator{}); - } - - // container + default_getter + default_comparator - template - requires getter_t - [[nodiscard]] constexpr auto match( - const Container& container // - ) const noexcept(noexcept(default_comparator{}( - default_getter{}(container, 0), - rep_value()[0] - ))) -> bool // - requires(not lazy_is_static()) - { - // - return this->template match>( - container, - default_getter{}, - default_comparator{}); - } - - // container + default_getter + default_comparator - template - requires getter_t - [[nodiscard]] constexpr static auto match( - const Container& container // - ) noexcept(noexcept(default_comparator{}( - default_getter{}(container, 0), - rep_value()[0] - ))) -> bool // - requires(lazy_is_static()) - { - // - return meta_string_base::match>( - container, - default_getter{}, - default_comparator{}); - } - }; - - template - [[nodiscard]] consteval auto contains_zero() noexcept -> bool - { - if constexpr (sizeof...(Cs) == 0) { return This == static_cast(0); } - else { return contains_zero(); } - } - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - /** - * @brief An immutable array of fixed-length characters. - */ - template - struct basic_char_array; - - template - using basic_char_array_view = string_detail::default_view; - - /** - * @brief A mutable array of fixed-length characters. - */ - template - struct basic_fixed_string; - - template - using basic_fixed_string_view = string_detail::default_view; - - template - struct is_basic_char_array : std::false_type {}; - - template - struct is_basic_char_array> : std::true_type {}; - - template - constexpr auto is_basic_char_array_v = is_basic_char_array::value; - - template - struct is_basic_fixed_string : std::false_type {}; - - template - struct is_basic_fixed_string> : std::true_type {}; - - template - constexpr auto is_basic_fixed_string_v = is_basic_fixed_string::value; - - template - concept basic_char_array_t = is_basic_char_array_v; - - template - concept basic_fixed_string_t = is_basic_fixed_string_v; - - template - struct basic_char_array : string_detail::meta_string_base, T, std::size_t> - { - using base_type = string_detail::meta_string_base; - - using value_type = T; - using size_type = std::size_t; - - using const_pointer = const value_type*; - - constexpr static size_type max_size = sizeof...(Cs); - constexpr static value_type value[max_size]{Cs...}; - constexpr static size_type size = max_size - (value[max_size - 1] == '\0'); - - [[nodiscard]] constexpr static const_pointer begin() noexcept { return std::ranges::begin(value); } - - [[nodiscard]] constexpr static const_pointer end() noexcept { return std::ranges::end(value); } - - template - requires(N <= max_size) - [[nodiscard]] constexpr static auto as_fixed_string() noexcept - { - return basic_fixed_string{basic_fixed_string_view{value, N}}; - } - - // // basic_char_array <=> basic_char_array - // template - // [[nodiscard]] constexpr auto operator<=>(const basic_char_array& rhs) const noexcept -> auto - // { - // // - // return this->template as>() <=> rhs.template as>(); - // } - // - // // basic_char_array <=> string - // template - // requires(not basic_char_array_t) and std::is_constructible_v, String> - // [[nodiscard]] constexpr auto operator<=>(const String& rhs) const noexcept(std::is_nothrow_constructible_v, String>) -> auto - // { - // // - // return this->template as>() <=> basic_char_array_view{rhs}; - // } - // - // // string <=> basic_char_array - // template - // requires(not basic_char_array_t) and std::is_constructible_v, String> - // [[nodiscard]] friend constexpr auto operator<=>(const String& lhs, const basic_char_array& rhs) noexcept(std::is_nothrow_constructible_v, String>) -> auto - // { - // // - // return basic_char_array_view{lhs} <=> rhs.template as>(); - // } - }; - - template - [[nodiscard]] constexpr auto to_char_array() noexcept -> auto - { - using fixed_string_type = decltype(FixedString); - - return [](const std::index_sequence) noexcept - { - return basic_char_array{}; - }(std::make_index_sequence{}); - } - - template - using char_array = basic_char_array; - template - using wchar_array = basic_char_array; - template - // ReSharper disable once CppInconsistentNaming - using u8char_array = basic_char_array; - template - // ReSharper disable once CppInconsistentNaming - using u16char_array = basic_char_array; - template - // ReSharper disable once CppInconsistentNaming - using u32char_array = basic_char_array; - - template - struct basic_fixed_string : string_detail::meta_string_base, T, std::size_t> - { - using base_type = string_detail::meta_string_base; - - using value_type = T; - using size_type = std::size_t; - - using pointer = value_type*; - using const_pointer = const value_type*; - - constexpr static size_type max_size{N}; - constexpr static size_type size{max_size - 1}; - value_type value[max_size]{}; - - constexpr basic_fixed_string() noexcept = default; - - template - requires(M >= N) - constexpr explicit(false) basic_fixed_string(const value_type (&string)[M]) noexcept - { - std::ranges::copy(std::ranges::begin(string), std::ranges::begin(string) + size, value); - } - - template - requires( - // DO NOT USE `basic_char_array::size`! - sizeof...(Cs) - ((Cs == 0) or ...) >= N) - constexpr explicit(false) basic_fixed_string(const basic_char_array& char_array) noexcept - { - std::ranges::copy(std::ranges::begin(char_array), std::ranges::begin(char_array) + N, value); - } - - template - requires std::is_same_v, value_type> - constexpr explicit basic_fixed_string(const String& string) noexcept - { - std::ranges::copy(std::ranges::begin(string), std::ranges::begin(string) + N, value); - } - - [[nodiscard]] constexpr auto begin() noexcept -> pointer { return value; } - - [[nodiscard]] constexpr auto begin() const noexcept -> const_pointer { return value; } - - [[nodiscard]] constexpr auto end() noexcept -> pointer { return value + size; } - - [[nodiscard]] constexpr auto end() const noexcept -> const_pointer { return value + size; } - - // // basic_fixed_string <=> basic_fixed_string - // template - // [[nodiscard]] constexpr auto operator<=>(const basic_fixed_string& rhs) const noexcept -> auto - // { - // // - // return this->template as>() <=> rhs.template as>(); - // } - // - // // basic_fixed_string <=> string - // template - // requires(not basic_fixed_string_t) and std::is_constructible_v, String> - // [[nodiscard]] constexpr auto operator<=>(const String& rhs) const noexcept(std::is_nothrow_constructible_v, String>) -> auto - // { - // // - // return this->template as>() <=> basic_fixed_string_view{rhs}; - // } - // - // // string <=> basic_fixed_string - // template - // requires(not basic_fixed_string_t) and std::is_constructible_v, String> - // [[nodiscard]] friend constexpr auto operator<=>(const String& lhs, const basic_fixed_string& rhs) noexcept(std::is_nothrow_constructible_v, String>) -> auto - // { - // // - // return basic_fixed_string_view{lhs} <=> rhs.template as>(); - // } - }; - - template - basic_fixed_string(const T (&string)[N]) -> basic_fixed_string; - - template - basic_fixed_string(basic_char_array char_array) -> basic_fixed_string< - T, - // DO NOT USE `basic_char_array::size`! - sizeof...(Cs) - string_detail::contains_zero() - >; - - template - using fixed_string = basic_fixed_string; - template - // ReSharper disable once IdentifierTypo - using fixed_wstring = basic_fixed_string; - template - // ReSharper disable once CppInconsistentNaming - using fixed_u8string = basic_fixed_string; - template - // ReSharper disable once CppInconsistentNaming - using fixed_u16string = basic_fixed_string; - template - // ReSharper disable once CppInconsistentNaming - using fixed_u32string = basic_fixed_string; - - using fixed_string_view = basic_fixed_string_view; - // ReSharper disable once IdentifierTypo - using fixed_wstring_view = basic_fixed_string_view; - // ReSharper disable once CppInconsistentNaming - using fixed_u8string_view = basic_fixed_string_view; - // ReSharper disable once CppInconsistentNaming - using fixed_u16string_view = basic_fixed_string_view; - // ReSharper disable once CppInconsistentNaming - using fixed_u32string_view = basic_fixed_string_view; - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -} - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE_STD -{ - // for `std::totally_ordered_with` - template typename Q1, template typename Q2> - requires std::is_constructible_v, String> - struct basic_common_reference // NOLINT(cert-dcl58-cpp) - { - using type = gal::prometheus::meta::basic_char_array_view; - }; - - template typename Q1, template typename Q2> - requires std::is_constructible_v, String> - struct basic_common_reference // NOLINT(cert-dcl58-cpp) - { - using type = gal::prometheus::meta::basic_char_array_view; - }; - - // for `std::totally_ordered_with` - template typename Q1, template typename Q2> - requires std::is_constructible_v, String> - struct basic_common_reference // NOLINT(cert-dcl58-cpp) - { - using type = gal::prometheus::meta::basic_fixed_string_view; - }; - - template typename Q1, template typename Q2> - requires std::is_constructible_v, String> - struct basic_common_reference // NOLINT(cert-dcl58-cpp) - { - using type = gal::prometheus::meta::basic_fixed_string_view; - }; -} diff --git a/src/meta/to_string.ixx b/src/meta/to_string.hpp similarity index 77% rename from src/meta/to_string.ixx rename to src/meta/to_string.hpp index 3d6456e4..2a4fa0dc 100644 --- a/src/meta/to_string.ixx +++ b/src/meta/to_string.hpp @@ -1,33 +1,22 @@ // This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal +// Copyright (C) 2022-2025 Life4gal // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.meta:to_string; - -import std; -import :member; -import :enumeration; - -#else #pragma once #include #include #include +#include #include -#include -#include -#endif +#include +#include +#include -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::meta) +namespace gal::prometheus::meta { template< std::ranges::output_range StringType = std::basic_string, @@ -45,12 +34,19 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::meta) if constexpr (ContainsTypeName) { std::format_to(std::back_inserter(out), "{}({})", meta::name_of(), t); } else { std::format_to(std::back_inserter(out), "{}", t); } } - // appendable - // note: prefer formattable than appendable - else if constexpr (requires { out.append(t); }) { out.append(t); } - else if constexpr (requires { out.emplace_back(t); }) { out.emplace_back(t); } - else if constexpr (requires { out.push_back(t); }) { out.push_back(t); } - else if constexpr (requires { out += t; }) { out += t; } + // // appendable + // // note: prefer formattable than appendable + // else if constexpr (requires { out.append(t); }) { out.append(t); } + // else if constexpr (requires { out.emplace_back(t); }) { out.emplace_back(t); } + // + // GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH + // GAL_PROMETHEUS_COMPILER_DISABLE_MSVC_WARNING(4244) + // + // else if constexpr (requires { out.push_back(t); }) { out.push_back(t); } + // + // GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP + // + // else if constexpr (requires { out += t; }) { out += t; } // construct from T else if constexpr (std::is_constructible_v) { @@ -61,6 +57,12 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::meta) else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE("not appendable."); } } // member function + else if constexpr (requires { t.to_string(out); }) + { + if constexpr (ContainsTypeName) { std::format_to(std::back_inserter(out), "{}(", meta::name_of()); } + t.to_string(out); + if constexpr (ContainsTypeName) { out.push_back(')'); } + } else if constexpr (requires { t.to_string(); }) { if constexpr (ContainsTypeName) { std::format_to(std::back_inserter(out), "{}(", meta::name_of()); } @@ -85,7 +87,7 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::meta) if constexpr (ContainsTypeName) { std::format_to(std::back_inserter(out), "{}(", meta::name_of()); } // address std::format_to(std::back_inserter(out), "0x{:x} => ", reinterpret_cast(t)); - // sub-element does not contains type name. + // sub-element does not contain type name. meta::to_string(*t, out); if constexpr (ContainsTypeName) { out.push_back(')'); } // } @@ -100,15 +102,15 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::meta) t, [&out](const E& element) noexcept -> decltype(auto) { - // sub-element does not contains type name. + // sub-element does not contain type name. meta::to_string(element, out); out.push_back(','); }); out.back() = ']'; } - // meta::member_gettable - else if constexpr (meta::member_gettable_t) + // meta::known_member_t + else if constexpr (meta::known_member_t) { if constexpr (ContainsTypeName) { std::format_to(std::back_inserter(out), "{}", meta::name_of()); } out.push_back('{'); @@ -120,7 +122,7 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::meta) [&out](const E& element) noexcept -> void { std::format_to(std::back_inserter(out), ".{} = ", meta::name_of_member()); - // sub-element does not contains type name. + // sub-element does not contain type name. meta::to_string(element, out); out.push_back(','); }, @@ -144,7 +146,8 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::meta) template< std::ranges::output_range StringType = std::basic_string, bool ContainsTypeName = true, - typename T> + typename T + > requires std::ranges::contiguous_range [[nodiscard]] constexpr auto to_string(const T& t) noexcept -> StringType { diff --git a/src/numeric/numeric.hpp b/src/numeric/numeric.hpp new file mode 100644 index 00000000..7fac90fd --- /dev/null +++ b/src/numeric/numeric.hpp @@ -0,0 +1,9 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include diff --git a/src/numeric/numeric.ixx b/src/numeric/numeric.ixx deleted file mode 100644 index ce1d46c2..00000000 --- a/src/numeric/numeric.ixx +++ /dev/null @@ -1,18 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -export module gal.prometheus.numeric; - -export import :random_engine; -export import :random; - -#else -#pragma once - -#include -#include - -#endif diff --git a/src/numeric/random.ixx b/src/numeric/random.hpp similarity index 89% rename from src/numeric/random.ixx rename to src/numeric/random.hpp index 3dec2649..72d51eca 100644 --- a/src/numeric/random.ixx +++ b/src/numeric/random.hpp @@ -1,21 +1,8 @@ // This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal +// Copyright (C) 2022-2025 Life4gal // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.numeric:random; - -import std; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -import :random_engine; - -#else #pragma once #include @@ -23,12 +10,12 @@ import :random_engine; #include #include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE -#endif +#include -#if __cpp_static_call_operator >= 202207L +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +#if defined(__cpp_static_call_operator) and __cpp_static_call_operator >= 202207L #define RANDOM_WORKAROUND_OPERATOR_STATIC static #define RANDOM_WORKAROUND_OPERATOR_CONST #define RANDOM_WORKAROUND_OPERATOR_THIS(type) type:: @@ -63,7 +50,8 @@ namespace gal::prometheus::numeric template typename Distribution, typename T> struct is_user_defined_distribution : std::true_type { - // We always assume that the target distribution contains static_assert (or concept) to restrict the type of T. If it does not, then we assume that it supports arbitrary types. + // We always assume that the target distribution contains static_assert (or concept) to restrict the type of T. + // If it does not, then we assume that it supports arbitrary types. static_assert( std::is_default_constructible_v> or std::is_constructible_v, T> or @@ -75,8 +63,6 @@ namespace gal::prometheus::numeric constexpr auto is_user_defined_distribution_v = is_user_defined_distribution::value; } - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - template typename, typename> struct is_distribution_compatible : std::false_type {}; @@ -167,7 +153,7 @@ namespace gal::prometheus::numeric template typename Distribution, typename T> concept distribution_compatible_t = is_distribution_compatible_v; - enum class RandomStateCategory + enum class RandomStateCategory : std::uint8_t { SHARED, SHARED_THREAD_ONLY, @@ -175,8 +161,8 @@ namespace gal::prometheus::numeric }; template< - RandomStateCategory Category, - typename RandomEngine, + RandomStateCategory Category = RandomStateCategory::PRIVATE, + typename RandomEngine = random_engine_xrsr_128_star_star, template typename IntegerDistribution = default_int_distribution, template @@ -186,7 +172,7 @@ namespace gal::prometheus::numeric { public: constexpr static auto category = Category; - constexpr static auto is_shared_category = category == RandomStateCategory::SHARED or category == RandomStateCategory::SHARED_THREAD_ONLY; + constexpr static bool is_shared_category = category == RandomStateCategory::SHARED or category == RandomStateCategory::SHARED_THREAD_ONLY; using engine_type = RandomEngine; @@ -209,7 +195,7 @@ namespace gal::prometheus::numeric static engine_type engine{}; return engine; } - else if (category == RandomStateCategory::SHARED_THREAD_ONLY) + else if constexpr (category == RandomStateCategory::SHARED_THREAD_ONLY) { thread_local engine_type engine{}; return engine; @@ -234,9 +220,18 @@ namespace gal::prometheus::numeric template requires std::is_constructible_v constexpr explicit Random(Args&&... args) noexcept(noexcept(std::is_nothrow_constructible_v)) // - requires(not is_shared_category) // PRIVATE ONLY + requires(not is_shared_category and sizeof...(args) != 0) // PRIVATE ONLY : real_engine_{.engine = engine_type{std::forward(args)...}} {} + // sizeof...(args) != 0 + Random() => Random random{}; + constexpr explicit Random() noexcept(std::is_nothrow_default_constructible_v) // + requires(not is_shared_category) // PRIVATE ONLY + : real_engine_{} {} + + // It doesn't make sense, but it's allowed :) + constexpr explicit Random() noexcept // + requires(is_shared_category) = default; + [[nodiscard]] constexpr static auto min() noexcept -> result_type { return engine_type::min(); } [[nodiscard]] constexpr static auto max() noexcept -> result_type { return engine_type::max(); } @@ -245,7 +240,10 @@ namespace gal::prometheus::numeric const result_type new_seed = static_cast(std::chrono::steady_clock::now().time_since_epoch().count())) noexcept(noexcept( engine().seed(new_seed) - )) -> void requires(is_shared_category) { engine().seed(new_seed); } + )) -> void requires(is_shared_category) + { + engine().seed(new_seed); + } constexpr auto seed(const result_type new_seed = static_cast(std::chrono::steady_clock::now().time_since_epoch().count())) noexcept(noexcept( @@ -392,8 +390,14 @@ namespace gal::prometheus::numeric )) -> Iterator requires(is_shared_category) { if (const auto diff = std::ranges::distance(from, to); - diff == 0) { return to; } - else { return std::ranges::next(from, Random::get::difference_type>(0, diff - 1)); } + diff == 0) + { + return to; + } + else + { + return std::ranges::next(from, Random::get::difference_type>(0, diff - 1)); + } } template @@ -423,8 +427,14 @@ namespace gal::prometheus::numeric )) -> Iterator requires(not is_shared_category) { if (const auto diff = std::ranges::distance(from, to); - diff == 0) { return to; } - else { return std::ranges::next(from, this->template get::difference_type>(0, diff - 1)); } + diff == 0) + { + return to; + } + else + { + return std::ranges::next(from, this->template get::difference_type>(0, diff - 1)); + } } template @@ -488,9 +498,9 @@ namespace gal::prometheus::numeric false #else std::ranges::generate_n( - std::back_inserter(container), - count, - [from, to]() noexcept(noexcept(Random::get(from, to))) { return Random::get(from, to); }) // + std::back_inserter(container), + count, + [from, to]() noexcept(noexcept(Random::get(from, to))) { return Random::get(from, to); }) // #endif )) -> void requires(is_shared_category) { @@ -579,7 +589,10 @@ namespace gal::prometheus::numeric { Container c{}; - if constexpr (requires { c.reserve(count); }) { c.reserve(count); } + if constexpr (requires { c.reserve(count); }) + { + c.reserve(count); + } Random::get(c, from, to, count); return c; @@ -608,7 +621,10 @@ namespace gal::prometheus::numeric { Container c{}; - if constexpr (requires { c.reserve(count); }) { c.reserve(count); } + if constexpr (requires { c.reserve(count); }) + { + c.reserve(count); + } this->get(c, from, to, count); return c; @@ -651,7 +667,20 @@ namespace gal::prometheus::numeric requires(not is_shared_category) { return this->template get(std::forward(args)...); } }; - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END + // clang / clang-cl + // error: ambiguous deduction for template arguments of 'Random' + // numeric::Random random{}; + // ^ + // note: candidate function [with Category = gal::prometheus::numeric::RandomStateCategory::PRIVATE, RandomEngine = gal::prometheus::numeric::RandomEngine, IntegerDistribution = gal::prometheus::numeric::default_int_distribution, FloatingPointDistribution = gal::prometheus::numeric::default_floating_point_distribution, BooleanDistribution = std::bernoulli_distribution] + // constexpr explicit Random() noexcept(std::is_nothrow_default_constructible_v) // + // ^ + // note: candidate function [with Category = gal::prometheus::numeric::RandomStateCategory::PRIVATE, RandomEngine = gal::prometheus::numeric::RandomEngine, IntegerDistribution = gal::prometheus::numeric::default_int_distribution, FloatingPointDistribution = gal::prometheus::numeric::default_floating_point_distribution, BooleanDistribution = std::bernoulli_distribution] + // constexpr explicit Random() noexcept // + // ^ + // fix 1: + // numeric::Random<> random{}; + // fix 2: + Random() -> Random<>; } #undef RANDOM_WORKAROUND_OPERATOR_STATIC diff --git a/src/numeric/random_engine.hpp b/src/numeric/random_engine.hpp new file mode 100644 index 00000000..980511c4 --- /dev/null +++ b/src/numeric/random_engine.hpp @@ -0,0 +1,1024 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +// A C++ implementation based on [http://prng.di.unimi.it/]. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace gal::prometheus::numeric +{ + namespace random_engine_detail + { + using bit64_type = std::uint64_t; + using bit32_type = std::uint32_t; + + template + constexpr auto rotate_left(const T value) noexcept -> T + { + return (value << N) | (value >> (Total - N)); + } + + template + constexpr auto rotate_left_to(T& value) noexcept -> void + { + value = random_engine_detail::rotate_left(value); + } + + template + class RandomEngineBase + { + [[nodiscard]] auto rep() noexcept -> Engine& { return static_cast(*this); } + + [[nodiscard]] auto rep() const noexcept -> const Engine& { return static_cast(*this); } + + public: + using result_type = std::conditional_t; + using state_type = std::array; + + constexpr static auto bits_of_this = std::numeric_limits::digits; + + /** + * Output: 64 bits + * Period: 2 ^ 64 + * Footprint: 8 bytes + * Original implementation: http://prng.di.unimi.it/splitmix64.c + */ + struct state_generator final + { + result_type seed; + + [[nodiscard]] constexpr auto state() noexcept -> state_type + { + state_type new_state{}; + std::ranges::generate(new_state, *this); + return new_state; + } + + [[nodiscard]] constexpr auto operator()() noexcept -> result_type + { + seed += static_cast(0x9e3779b97f4a7c15ull); + + auto z = seed; + z = static_cast((z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ull); + z = static_cast((z ^ (z >> 27)) * 0x94d049bb133111ebull); + return z ^ (z >> 31); + } + }; + + protected: + state_type state_; + + private: + constexpr auto do_jump(const state_type steps) noexcept -> void + { + state_type to{}; + + std::ranges::for_each( + steps, + [this, &to](const auto step) noexcept -> void + { + std::ranges::for_each( + std::views::iota(0, bits_of_this), + [this, &to, step](const auto mask) noexcept -> void + { + if (step & mask) + { + for (auto& [t, s]: std::views::zip(to, state_)) + { + t ^= s; + } + } + discard(); + }, + [](const auto this_bit) noexcept -> result_type + { + return result_type{1} << this_bit; + }); + }); + + state_.swap(to); + } + + public: + constexpr explicit RandomEngineBase(const state_type state) noexcept // NOLINT(bugprone-crtp-constructor-accessibility) + : state_{state} {} + + constexpr explicit RandomEngineBase(const result_type seed) noexcept // NOLINT(bugprone-crtp-constructor-accessibility) + : RandomEngineBase{state_generator{.seed = seed}.state()} {} + + constexpr explicit RandomEngineBase() noexcept // NOLINT(bugprone-crtp-constructor-accessibility) + : RandomEngineBase{state_generator{.seed = static_cast(std::random_device{}())}.state()} {} + + constexpr RandomEngineBase(RandomEngineBase&&) noexcept = default; // NOLINT(bugprone-crtp-constructor-accessibility) + constexpr auto operator=(RandomEngineBase&&) noexcept -> RandomEngineBase& = default; + + constexpr RandomEngineBase(const RandomEngineBase&) noexcept = delete; // NOLINT(bugprone-crtp-constructor-accessibility) + constexpr auto operator=(const RandomEngineBase&) noexcept -> RandomEngineBase& = delete; + + constexpr ~RandomEngineBase() noexcept = default; + + [[nodiscard]] constexpr static auto min() noexcept -> result_type { return std::numeric_limits::lowest(); } + + [[nodiscard]] constexpr static auto max() noexcept -> result_type { return std::numeric_limits::max(); } + + constexpr auto seed(const result_type new_seed) noexcept -> void { *this = RandomEngineBase{new_seed}; } + + [[nodiscard]] constexpr auto peek() const noexcept -> result_type { return rep().do_peek(); } + + constexpr auto next() noexcept -> result_type { return rep().do_next(); } + + constexpr auto discard(const result_type count) noexcept -> void + { + // fixme + for (result_type i = 0; i < count; ++i) { next(); } + } + + [[nodiscard]] constexpr auto operator()() noexcept -> result_type { return next(); } + + constexpr auto jump() noexcept -> void { rep().do_jump(rep().do_jump_state()); } + + constexpr auto long_jump() noexcept -> void { rep().do_jump(rep().do_long_jump_state()); } + }; + + template + class Jumper; + + template + class Jumper + { + public: + using engine_type = RandomEngineBase; + + using result_type = typename engine_type::result_type; + using state_type = typename engine_type::state_type; + + constexpr static auto rotate(state_type& state) noexcept -> void + { + const result_type t = state[1] << 9; + + state[2] ^= state[0]; + state[3] ^= state[1]; + state[1] ^= state[2]; + state[0] ^= state[3]; + + state[2] ^= t; + random_engine_detail::rotate_left_to<11, engine_type::bits_of_this>(state[3]); + } + + /** + * @brief This is the jump function for the generator. It is equivalent + * to 2 ^ 64 calls to operator(); it can be used to generate 2 ^ 64 + * non-overlapping sub-sequences for parallel computations. + * @return generated jump steps + */ + [[nodiscard]] constexpr static auto jump() noexcept -> state_type + { + return { + static_cast(0x8764000bull), + static_cast(0xf542d2d3ull), + static_cast(0x6fa035c3ull), + static_cast(0x77f2db5bull)}; + } + + /** + * @brief This is the long-jump function for the generator. It is equivalent to + * 2 ^ 96 calls to operator(); it can be used to generate 2 ^ 32 starting points, + * from each of which jump() will generate 2 ^ 32 non-overlapping + * sub-sequences for parallel distributed computations. + * @return generated long jump steps + */ + [[nodiscard]] constexpr static auto long_jump() noexcept -> state_type + { + return { + static_cast(0xb523952eull), + static_cast(0x0b6f099full), + static_cast(0xccf5a0efull), + static_cast(0x1c580662ull)}; + } + }; + + template + class Jumper + { + public: + using engine_type = RandomEngineBase; + + using result_type = typename engine_type::result_type; + using state_type = typename engine_type::state_type; + + /** + * @brief This is the jump function for the generator. It is equivalent + * to 2 ^ 64 calls to next(); it can be used to generate 2 ^ 64 + * non-overlapping sub-sequences for parallel computations. + * @return generated jump steps + */ + [[nodiscard]] constexpr static auto jump() noexcept -> state_type + { + return { + static_cast(0xdf900294d8f554a5ull), + static_cast(0x170865df4b3201fcull) + }; + } + + /** + * @brief This is the long-jump function for the generator. It is equivalent to + * 2 ^ 96 calls to next(); it can be used to generate 2 ^ 32 starting points, + * from each of which jump() will generate 2 ^ 32 non-overlapping + * sub-sequences for parallel distributed computations. + * @return generated long jump steps + */ + [[nodiscard]] constexpr static auto long_jump() noexcept -> state_type + { + return { + static_cast(0xd2a98b26625eee7bull), + static_cast(0xdddf9b1090aa7ac1ull) + }; + } + }; + + template + class Jumper + { + public: + using engine_type = RandomEngineBase; + + using result_type = typename engine_type::result_type; + using state_type = typename engine_type::state_type; + + constexpr static auto rotate(state_type& state) noexcept -> void + { + const result_type t = state[1] << 17; + + state[2] ^= state[0]; + state[3] ^= state[1]; + state[1] ^= state[2]; + state[0] ^= state[3]; + + state[2] ^= t; + random_engine_detail::rotate_left_to<45, engine_type::bits_of_this>(state[3]); + } + + /** + * @brief This is the jump function for the generator. It is equivalent + * to 2 ^ 128 calls to operator(); it can be used to generate 2 ^ 128 + * non-overlapping sub-sequences for parallel computations. + * @return generated jump steps + */ + [[nodiscard]] constexpr static auto jump() noexcept -> state_type + { + return { + static_cast(0x180ec6d33cfd0abaull), + static_cast(0xd5a61266f0c9392cull), + static_cast(0xa9582618e03fc9aaull), + static_cast(0x39abdc4529b1661cull) + }; + } + + /** + * @brief This is the long-jump function for the generator. It is equivalent to + * 2 ^ 192 calls to operator(); it can be used to generate 2 ^ 64 starting points, + * from each of which jump() will generate 2 ^ 64 non-overlapping + * sub-sequences for parallel distributed computations. + * @return generated long jump steps + */ + [[nodiscard]] constexpr static auto long_jump() noexcept -> state_type + { + return { + static_cast(0x76e15d3efefdcbbfull), + static_cast(0xc5004e441c522fb3ull), + static_cast(0x77710069854ee241ull), + static_cast(0x39109bb02acbe635ull) + }; + } + }; + + template + class Jumper + { + public: + using engine_type = RandomEngineBase; + + using result_type = typename engine_type::result_type; + using state_type = typename engine_type::state_type; + + constexpr static auto rotate(state_type& state) noexcept -> void + { + const result_type t = state[1] << 11; + + state[2] ^= state[0]; + state[5] ^= state[1]; + state[1] ^= state[2]; + state[7] ^= state[3]; + state[3] ^= state[4]; + state[4] ^= state[5]; + state[0] ^= state[6]; + state[6] ^= state[7]; + + state[6] ^= t; + random_engine_detail::rotate_left_to<21, engine_type::bits_of_this>(state[7]); + } + + /** + * @brief This is the jump function for the generator. It is equivalent + * to 2 ^ 256 calls to operator(); it can be used to generate 2 ^ 256 + * non-overlapping sub-sequences for parallel computations. + * @return generated jump steps + */ + [[nodiscard]] constexpr static auto jump() noexcept -> state_type + { + return { + static_cast(0x33ed89b6e7a353f9ull), + static_cast(0x760083d7955323beull), + static_cast(0x2837f2fbb5f22faeull), + static_cast(0x4b8c5674d309511cull), + static_cast(0xb11ac47a7ba28c25ull), + static_cast(0xf1be7667092bcc1cull), + static_cast(0x53851efdb6df0aafull), + static_cast(0x1ebbc8b23eaf25dbull) + }; + } + + /** + * @brief This is the long-jump function for the generator. It is equivalent to + * 2 ^ 384 calls to operator(); it can be used to generate 2 ^ 128 starting points, + * from each of which jump() will generate 2 ^ 128 non-overlapping + * sub-sequences for parallel distributed computations. + * @return generated long jump steps + */ + [[nodiscard]] constexpr static auto long_jump() noexcept -> state_type + { + return { + static_cast(0x11467fef8f921d28ull), + static_cast(0xa2a819f2e79c8ea8ull), + static_cast(0xa8299fc284b3959aull), + static_cast(0xb4d347340ca63ee1ull), + static_cast(0x1cb0940bedbff6ceull), + static_cast(0xd956c5c4fa1f8e17ull), + static_cast(0x915e38fd4eda93bcull), + static_cast(0x5b3ccdfa5d7daca5ull) + }; + } + }; + } + + enum class RandomEngineCategory : std::uint8_t + { + // xor + shift + rotate + X_S_R, + // xor + rotate + shift + rotate + X_R_S_R, + }; + + enum class RandomEngineTag : std::uint8_t + { + PLUS, + PLUS_PLUS, + STAR_STAR, + }; + + enum class RandomEngineBit : std::uint8_t + { + BITS_128, + BITS_256, + BITS_512, + }; + + template + class RandomEngine; + + /** + * @brief Fastest 32-bit generator for 32-bit floating-point numbers. + * We suggest to use its upper bits for floating-point generation, + * if low linear complexity is not considered an issue (as it is usually + * the case) it can be used to generate 32-bit outputs, too. + * We suggest to use a sign test to extract a random Boolean value, and + * right shifts to extract subsets of bits. + * @note + * Output: 32 bits \n + * Period: 2 ^ 128 - 1 \n + * Footprint: 16 bytes \n + * @see http://prng.di.unimi.it/xoshiro128plus.c + */ + template<> + class RandomEngine + : public + random_engine_detail::RandomEngineBase< + random_engine_detail::bit32_type, + 4, + RandomEngine + > + { + friend RandomEngineBase; + + public: + using jumper = random_engine_detail::Jumper; + + using RandomEngineBase::RandomEngineBase; + + private: + [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type { return state_[0] + state_[3]; } + + constexpr auto do_next() noexcept -> result_type + { + const auto result = do_peek(); + jumper::rotate(state_); + return result; + } + + [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } + + [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } + }; + + using random_engine_xsr_128_plus = RandomEngine; + + static_assert(std::uniform_random_bit_generator); + static_assert(sizeof(random_engine_xsr_128_plus) == 128 / CHAR_BIT); + + /** + * @brief 32-bit all-purpose, rock-solid generators. + * It has excellent speed, a state size (128 bits) that is large enough for mild parallelism. + * @note + * Output: 32 bits \n + * Period: 2 ^ 128 - 1 \n + * Footprint: 16 bytes \n + * @see http://prng.di.unimi.it/xoshiro128plusplus.c + */ + template<> + class RandomEngine + : public + random_engine_detail::RandomEngineBase< + random_engine_detail::bit32_type, + 4, + RandomEngine + > + { + friend RandomEngineBase; + + public: + using jumper = random_engine_detail::Jumper; + + using RandomEngineBase::RandomEngineBase; + + private: + [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type + { + return random_engine_detail::rotate_left<7, bits_of_this>(state_[0] + state_[3]) + state_[0]; + } + + constexpr auto do_next() noexcept -> result_type + { + const auto result = do_peek(); + jumper::rotate(state_); + return result; + } + + [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } + + [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } + }; + + using random_engine_xsr_128_plus_plus = RandomEngine; + + static_assert(std::uniform_random_bit_generator); + static_assert(sizeof(random_engine_xsr_128_plus_plus) == 128 / CHAR_BIT); + + /** + * @brief 32-bit all-purpose, rock-solid generators. + * It has excellent speed, a state size (128 bits) that is large enough for mild parallelism. + * @note + * Output: 32 bits \n + * Period: 2 ^ 128 - 1 \n + * Footprint: 16 bytes \n + * @see http://prng.di.unimi.it/xoshiro128starstar.c + */ + template<> + class RandomEngine + : public + random_engine_detail::RandomEngineBase< + random_engine_detail::bit32_type, + 4, + RandomEngine + > + { + friend RandomEngineBase; + + public: + using jumper = random_engine_detail::Jumper; + + using RandomEngineBase::RandomEngineBase; + + private: + [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type + { + return random_engine_detail::rotate_left<7, bits_of_this>(state_[1] * 5) * 9; + } + + constexpr auto do_next() noexcept -> result_type + { + const auto result = do_peek(); + jumper::rotate(state_); + return result; + } + + [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } + + [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } + }; + + using random_engine_xsr_128_star_star = RandomEngine; + + static_assert(std::uniform_random_bit_generator); + static_assert(sizeof(random_engine_xsr_128_star_star) == 128 / CHAR_BIT); + + /** + * @brief Fastest small-state generator for floating-point numbers, + * but its state space is large enough only for mild parallelism. + * We suggest to use its upper bits for floating-point generation, + * if low linear complexity is not considered an issue (as it is usually + * the case) it can be used to generate 64-bit outputs, too. + * We suggest to use a sign test to extract a random Boolean value, and + * right shifts to extract subsets of bits. + * @note + * Output: 64 bits \n + * Period: 2 ^ 128 - 1 \n + * Footprint: 16 bytes \n + * @see http://prng.di.unimi.it/xoroshiro128plus.c + */ + template<> + class RandomEngine + : public + random_engine_detail::RandomEngineBase< + random_engine_detail::bit64_type, + 2, + RandomEngine + > + { + friend RandomEngineBase; + + public: + using jumper = random_engine_detail::Jumper; + + using RandomEngineBase::RandomEngineBase; + + private: + [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type { return state_[0] + state_[1]; } + + constexpr auto do_next() noexcept -> result_type + { + const auto result = do_peek(); + + const auto s1 = state_[1] ^ state_[0]; + + state_[0] = random_engine_detail::rotate_left<24, bits_of_this>(state_[0]) ^ s1 ^ (s1 << 16); + state_[1] = random_engine_detail::rotate_left<37, bits_of_this>(s1); + + return result; + } + + [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } + + [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } + }; + + // ReSharper disable once IdentifierTypo + using random_engine_xrsr_128_plus = RandomEngine; + + static_assert(std::uniform_random_bit_generator); + static_assert(sizeof(random_engine_xrsr_128_plus) == 128 / CHAR_BIT); + + /** + * @brief All-purpose, rock-solid, small-state generators, + * its state space is large enough only for mild parallelism. + * @note + * Output: 64 bits \n + * Period: 2 ^ 128 - 1 \n + * Footprint: 16 bytes \n + * @see http://prng.di.unimi.it/xoroshiro128plusplus.c + */ + template<> + class RandomEngine + : public + random_engine_detail::RandomEngineBase< + random_engine_detail::bit64_type, + 2, + RandomEngine + > + { + friend RandomEngineBase; + + public: + using jumper = random_engine_detail::Jumper; + + using RandomEngineBase::RandomEngineBase; + + private: + [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type + { + return random_engine_detail::rotate_left<17, bits_of_this>(state_[0] + state_[1]) + state_[0]; + } + + constexpr auto do_next() noexcept -> result_type + { + const auto result = do_peek(); + + const auto s1 = state_[1] ^ state_[0]; + + state_[0] = random_engine_detail::rotate_left<49, bits_of_this>(state_[0]) ^ s1 ^ (s1 << 21); + state_[1] = random_engine_detail::rotate_left<28, bits_of_this>(s1); + + return result; + } + + /** + * @brief This is the jump function for the generator. It is equivalent + * to 2 ^ 64 calls to operator(); it can be used to generate 2 ^ 64 + * non-overlapping sub-sequences for parallel computations. + * @return generated jump steps + */ + [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type + { + return { + static_cast(0x2bd7a6a6e99c2ddcull), + static_cast(0x0992ccaf6a6fca05ull) + }; + } + + /** + * @brief This is the long-jump function for the generator. It is equivalent to + * 2 ^ 96 calls to operator(); it can be used to generate 2 ^ 32 starting points, + * from each of which jump() will generate 2 ^ 32 non-overlapping + * sub-sequences for parallel distributed computations. + * @return generated jump steps + */ + [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type + { + return { + static_cast(0x360fd5f2cf8d5d99ull), + static_cast(0x9c6e6877736c46e3ull) + }; + } + }; + + // ReSharper disable once IdentifierTypo + using random_engine_xrsr_128_plus_plus = RandomEngine; + + static_assert(std::uniform_random_bit_generator); + static_assert(sizeof(random_engine_xrsr_128_plus_plus) == 128 / CHAR_BIT); + + /** + * @brief All-purpose, rock-solid, small-state generators, + * its state space is large enough only for mild parallelism. + * @note + * Output: 64 bits \n + * Period: 2 ^ 128 - 1 \n + * Footprint: 16 bytes \n + * @see http://prng.di.unimi.it/xoroshiro128starstar.c + */ + template<> + class RandomEngine + : public + random_engine_detail::RandomEngineBase< + random_engine_detail::bit64_type, + 2, + RandomEngine + > + { + friend RandomEngineBase; + + public: + using jumper = random_engine_detail::Jumper; + + using RandomEngineBase::RandomEngineBase; + + private: + [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type + { + return random_engine_detail::rotate_left<7, bits_of_this>(state_[0] * 5) * 9; + } + + constexpr auto do_next() noexcept -> result_type + { + const auto result = do_peek(); + + const auto s1 = state_[1] ^ state_[0]; + + state_[0] = random_engine_detail::rotate_left<24, bits_of_this>(state_[0]) ^ s1 ^ (s1 << 16); + state_[1] = random_engine_detail::rotate_left<37, bits_of_this>(s1); + + return result; + } + + [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } + + [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } + }; + + // ReSharper disable once IdentifierTypo + using random_engine_xrsr_128_star_star = RandomEngine; + + static_assert(std::uniform_random_bit_generator); + static_assert(sizeof(random_engine_xrsr_128_star_star) == 128 / CHAR_BIT); + + /** + * @brief Fastest generator for floating-point numbers. + * We suggest to use its upper bits for floating-point generation, + * if low linear complexity is not considered an issue (as it is usually the case) it + * can be used to generate 64-bit outputs, too. + * @note + * Output: 64 bits \n + * Period: 2 ^ 256 - 1 \n + * Footprint: 32 bytes \n + * @see http://prng.di.unimi.it/xoshiro256plus.c + */ + template<> + class RandomEngine + : public + random_engine_detail::RandomEngineBase< + random_engine_detail::bit64_type, + 4, + RandomEngine + > + { + friend RandomEngineBase; + + public: + using jumper = random_engine_detail::Jumper; + + using RandomEngineBase::RandomEngineBase; + + private: + [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type { return state_[0] + state_[3]; } + + constexpr auto do_next() noexcept -> result_type + { + const auto result = do_peek(); + jumper::rotate(state_); + return result; + } + + [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } + + [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } + }; + + using random_engine_xsr_256_plus = RandomEngine; + + static_assert(std::uniform_random_bit_generator); + static_assert(sizeof(random_engine_xsr_256_plus) == 256 / CHAR_BIT); + + /** + * @brief All-purpose, rock-solid generators, + * it has excellent (sub-ns) speed, a state (256 bits) that is large + * enough for any parallel application. + * @note + * Output: 64 bits \n + * Period: 2 ^ 256 - 1 \n + * Footprint: 32 bytes \n + * @see http://prng.di.unimi.it/xoshiro256plusplus.c + */ + template<> + class RandomEngine + : public + random_engine_detail::RandomEngineBase< + random_engine_detail::bit64_type, + 4, + RandomEngine + > + { + friend RandomEngineBase; + + public: + using jumper = random_engine_detail::Jumper; + + using RandomEngineBase::RandomEngineBase; + + private: + [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type + { + return random_engine_detail::rotate_left<23, bits_of_this>(state_[0] + state_[3]) + state_[0]; + } + + constexpr auto do_next() noexcept -> result_type + { + const auto result = do_peek(); + jumper::rotate(state_); + return result; + } + + [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } + + [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } + }; + + using random_engine_xsr_256_plus_plus = RandomEngine; + + static_assert(std::uniform_random_bit_generator); + static_assert(sizeof(random_engine_xsr_256_plus_plus) == 256 / CHAR_BIT); + + /** + * @brief All-purpose, rock-solid generators, + * it has excellent (sub-ns) speed, a state (256 bits) that is large + * enough for any parallel application. + * @note + * Output: 64 bits \n + * Period: 2 ^ 256 - 1 \n + * Footprint: 32 bytes \n + * @see http://prng.di.unimi.it/xoshiro256starstar.c + */ + template<> + class RandomEngine + : public + random_engine_detail::RandomEngineBase< + random_engine_detail::bit64_type, + 4, + RandomEngine + > + { + friend RandomEngineBase; + + public: + using jumper = random_engine_detail::Jumper; + + using RandomEngineBase::RandomEngineBase; + + private: + [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type + { + return random_engine_detail::rotate_left<7, bits_of_this>(state_[1] * 5) * 9; + } + + constexpr auto do_next() noexcept -> result_type + { + const auto result = do_peek(); + jumper::rotate(state_); + return result; + } + + [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } + + [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } + }; + + using random_engine_xsr_256_star_star = RandomEngine; + + static_assert(std::uniform_random_bit_generator); + static_assert(sizeof(random_engine_xsr_256_star_star) == 256 / CHAR_BIT); + + /** + * @brief Generator for floating-point numbers with increased state size. + * We suggest to use its upper bits for floating-point generation, + * if low linear complexity is not considered an issue (as it is usually the case) it + * can be used to generate 64-bit outputs, too. + * @note + * Output: 64 bits \n + * Period: 2 ^ 512 - 1 \n + * Footprint: 64 bytes \n + * @see http://prng.di.unimi.it/xoshiro512plus.c + */ + template<> + class RandomEngine + : public + random_engine_detail::RandomEngineBase< + random_engine_detail::bit64_type, + 8, + RandomEngine + > + { + friend RandomEngineBase; + + public: + using jumper = random_engine_detail::Jumper; + + using RandomEngineBase::RandomEngineBase; + + private: + [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type { return state_[0] + state_[3]; } + + constexpr auto do_next() noexcept -> result_type + { + const auto result = do_peek(); + jumper::rotate(state_); + return result; + } + + [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } + + [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } + }; + + using random_engine_xsr_512_plus = RandomEngine; + + static_assert(std::uniform_random_bit_generator); + static_assert(sizeof(random_engine_xsr_512_plus) == 512 / CHAR_BIT); + + /** + * @brief All-purpose, rock-solid generators, + * it has excellent (sub-ns) speed, a state (512 bits) that is large + * enough for any parallel application. + * @note + * Output: 64 bits \n + * Period: 2 ^ 512 - 1 \n + * Footprint: 64 bytes \n + * @see http://prng.di.unimi.it/xoshiro512plusplus.c + */ + template<> + class RandomEngine + : public + random_engine_detail::RandomEngineBase< + random_engine_detail::bit64_type, + 8, + RandomEngine + > + { + friend RandomEngineBase; + + public: + using jumper = random_engine_detail::Jumper; + + using RandomEngineBase::RandomEngineBase; + + private: + [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type + { + return random_engine_detail::rotate_left<17, bits_of_this>(state_[0] + state_[2]) + state_[2]; + } + + constexpr auto do_next() noexcept -> result_type + { + const auto result = do_peek(); + jumper::rotate(state_); + return result; + } + + [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } + + [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } + }; + + using random_engine_xsr_512_plus_plus = RandomEngine; + + static_assert(std::uniform_random_bit_generator); + static_assert(sizeof(random_engine_xsr_512_plus_plus) == 512 / CHAR_BIT); + + /** + * @brief All-purpose, rock-solid generators, + * it has excellent (sub-ns) speed, a state (512 bits) that is large + * enough for any parallel application. + * @note + * Output: 64 bits \n + * Period: 2 ^ 512 - 1 \n + * Footprint: 32 bytes \n + * @see http://prng.di.unimi.it/xoshiro512starstar.c + */ + template<> + class RandomEngine + : public + random_engine_detail::RandomEngineBase< + random_engine_detail::bit64_type, + 8, + RandomEngine + > + { + friend RandomEngineBase; + + public: + using jumper = random_engine_detail::Jumper; + + using RandomEngineBase::RandomEngineBase; + + private: + [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type + { + return random_engine_detail::rotate_left<7, bits_of_this>(state_[1] * 5) * 9; + } + + constexpr auto do_next() noexcept -> result_type + { + const auto result = do_peek(); + jumper::rotate(state_); + return result; + } + + [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } + + [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } + }; + + using random_engine_xsr_512_star_star = RandomEngine; + + static_assert(std::uniform_random_bit_generator); + static_assert(sizeof(random_engine_xsr_512_star_star) == 512 / CHAR_BIT); +} diff --git a/src/numeric/random_engine.ixx b/src/numeric/random_engine.ixx deleted file mode 100644 index 4c0f95ec..00000000 --- a/src/numeric/random_engine.ixx +++ /dev/null @@ -1,1028 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -// A C++ implementation based on [http://prng.di.unimi.it/]. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.numeric:random_engine; - -import std; - -#else -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#endif - -#if not defined(CHAR_BIT) -#define CHAR_BIT std::numeric_limits::digits -#endif - -namespace gal::prometheus::numeric -{ - namespace random_engine_detail - { - using bit64_type = std::uint64_t; - using bit32_type = std::uint32_t; - - template - constexpr auto rotate_left(const T value) noexcept -> T { return (value << N) | (value >> (Total - N)); } - - template - constexpr auto rotate_left_to(T& value) noexcept -> void { value = rotate_left(value); } - - template - class RandomEngineBase - { - [[nodiscard]] auto rep() noexcept -> Engine& { return static_cast(*this); } - - [[nodiscard]] auto rep() const noexcept -> const Engine& { return static_cast(*this); } - - public: - using result_type = std::conditional_t; - using state_type = std::array; - - constexpr static auto bits_of_this = std::numeric_limits::digits; - - /** - * Output: 64 bits - * Period: 2 ^ 64 - * Footprint: 8 bytes - * Original implementation: http://prng.di.unimi.it/splitmix64.c - */ - struct state_generator final - { - result_type seed; - - [[nodiscard]] constexpr auto state() noexcept -> state_type - { - state_type new_state{}; - std::ranges::generate(new_state, *this); - return new_state; - } - - [[nodiscard]] constexpr auto operator()() noexcept -> result_type - { - seed += static_cast(0x9e3779b97f4a7c15ull); - - auto z = seed; - z = static_cast((z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ull); - z = static_cast((z ^ (z >> 27)) * 0x94d049bb133111ebull); - return z ^ (z >> 31); - } - }; - - protected: - state_type state_; - - private: - constexpr auto do_jump(const state_type steps) noexcept -> void - { - state_type to{}; - - std::ranges::for_each( - steps, - [this, &to](const auto step) noexcept -> void - { - std::ranges::for_each( - std::views::iota(0, bits_of_this), - [this, &to, step](const auto mask) noexcept -> void - { - if (step & mask) { for (auto& [t, s]: std::views::zip(to, state_)) { t ^= s; } } - discard(); - }, - [](const auto this_bit) noexcept -> result_type { return result_type{1} << this_bit; }); - }); - - state_.swap(to); - } - - public: - constexpr explicit RandomEngineBase(const state_type state) noexcept - : state_{state} {} - - constexpr explicit RandomEngineBase(const result_type seed) noexcept - : RandomEngineBase{state_generator{.seed = seed}.state()} {} - - constexpr explicit RandomEngineBase() noexcept - : RandomEngineBase{state_generator{.seed = static_cast(std::random_device{}())}.state()} {} - - constexpr RandomEngineBase(const RandomEngineBase&) noexcept = delete; - constexpr RandomEngineBase(RandomEngineBase&&) noexcept = default; - constexpr auto operator=(const RandomEngineBase&) noexcept -> RandomEngineBase& = delete; - constexpr auto operator=(RandomEngineBase&&) noexcept -> RandomEngineBase& = default; - - // todo: MSVC ICE here - #if not defined(GAL_PROMETHEUS_COMPILER_MSVC) - constexpr - #endif - ~RandomEngineBase() noexcept = default; - - [[nodiscard]] constexpr static auto min() noexcept -> result_type { return std::numeric_limits::lowest(); } - - [[nodiscard]] constexpr static auto max() noexcept -> result_type { return std::numeric_limits::max(); } - - constexpr auto seed(const result_type new_seed) noexcept -> void { *this = RandomEngineBase{new_seed}; } - - [[nodiscard]] constexpr auto peek() const noexcept -> result_type { return rep().do_peek(); } - - constexpr auto next() noexcept -> result_type { return rep().do_next(); } - - constexpr auto discard(const result_type count) noexcept -> void - { - // fixme: How to discard values gracefully? - for (result_type i = 0; i < count; ++i) { next(); } - } - - [[nodiscard]] constexpr auto operator()() noexcept -> result_type { return next(); } - - constexpr auto jump() noexcept -> void { do_jump(rep().do_jump_state()); } - - constexpr auto long_jump() noexcept -> void { do_jump(rep().do_long_jump_state()); } - }; - - template - class Jumper; - - template - class Jumper - { - public: - using engine_type = RandomEngineBase; - - using result_type = typename engine_type::result_type; - using state_type = typename engine_type::state_type; - - constexpr static auto rotate(state_type& state) noexcept -> void - { - const result_type t = state[1] << 9; - - state[2] ^= state[0]; - state[3] ^= state[1]; - state[1] ^= state[2]; - state[0] ^= state[3]; - - state[2] ^= t; - rotate_left_to<11, engine_type::bits_of_this>(state[3]); - } - - /** - * @brief This is the jump function for the generator. It is equivalent - * to 2 ^ 64 calls to operator(); it can be used to generate 2 ^ 64 - * non-overlapping sub-sequences for parallel computations. - * @return generated jump steps - */ - [[nodiscard]] constexpr static auto jump() noexcept -> state_type - { - return { - static_cast(0x8764000bull), - static_cast(0xf542d2d3ull), - static_cast(0x6fa035c3ull), - static_cast(0x77f2db5bull)}; - } - - /** - * @brief This is the long-jump function for the generator. It is equivalent to - * 2 ^ 96 calls to operator(); it can be used to generate 2 ^ 32 starting points, - * from each of which jump() will generate 2 ^ 32 non-overlapping - * sub-sequences for parallel distributed computations. - * @return generated long jump steps - */ - [[nodiscard]] constexpr static auto long_jump() noexcept -> state_type - { - return { - static_cast(0xb523952eull), - static_cast(0x0b6f099full), - static_cast(0xccf5a0efull), - static_cast(0x1c580662ull)}; - } - }; - - template - class Jumper - { - public: - using engine_type = RandomEngineBase; - - using result_type = typename engine_type::result_type; - using state_type = typename engine_type::state_type; - - /** - * @brief This is the jump function for the generator. It is equivalent - * to 2 ^ 64 calls to next(); it can be used to generate 2 ^ 64 - * non-overlapping sub-sequences for parallel computations. - * @return generated jump steps - */ - [[nodiscard]] constexpr static auto jump() noexcept -> state_type - { - return { - static_cast(0xdf900294d8f554a5ull), - static_cast(0x170865df4b3201fcull) - }; - } - - /** - * @brief This is the long-jump function for the generator. It is equivalent to - * 2 ^ 96 calls to next(); it can be used to generate 2 ^ 32 starting points, - * from each of which jump() will generate 2 ^ 32 non-overlapping - * sub-sequences for parallel distributed computations. - * @return generated long jump steps - */ - [[nodiscard]] constexpr static auto long_jump() noexcept -> state_type - { - return { - static_cast(0xd2a98b26625eee7bull), - static_cast(0xdddf9b1090aa7ac1ull) - }; - } - }; - - template - class Jumper - { - public: - using engine_type = RandomEngineBase; - - using result_type = typename engine_type::result_type; - using state_type = typename engine_type::state_type; - - constexpr static auto rotate(state_type& state) noexcept -> void - { - const result_type t = state[1] << 17; - - state[2] ^= state[0]; - state[3] ^= state[1]; - state[1] ^= state[2]; - state[0] ^= state[3]; - - state[2] ^= t; - rotate_left_to<45, engine_type::bits_of_this>(state[3]); - } - - /** - * @brief This is the jump function for the generator. It is equivalent - * to 2 ^ 128 calls to operator(); it can be used to generate 2 ^ 128 - * non-overlapping sub-sequences for parallel computations. - * @return generated jump steps - */ - [[nodiscard]] constexpr static auto jump() noexcept -> state_type - { - return { - static_cast(0x180ec6d33cfd0abaull), - static_cast(0xd5a61266f0c9392cull), - static_cast(0xa9582618e03fc9aaull), - static_cast(0x39abdc4529b1661cull) - }; - } - - /** - * @brief This is the long-jump function for the generator. It is equivalent to - * 2 ^ 192 calls to operator(); it can be used to generate 2 ^ 64 starting points, - * from each of which jump() will generate 2 ^ 64 non-overlapping - * sub-sequences for parallel distributed computations. - * @return generated long jump steps - */ - [[nodiscard]] constexpr static auto long_jump() noexcept -> state_type - { - return { - static_cast(0x76e15d3efefdcbbfull), - static_cast(0xc5004e441c522fb3ull), - static_cast(0x77710069854ee241ull), - static_cast(0x39109bb02acbe635ull) - }; - } - }; - - template - class Jumper - { - public: - using engine_type = RandomEngineBase; - - using result_type = typename engine_type::result_type; - using state_type = typename engine_type::state_type; - - constexpr static auto rotate(state_type& state) noexcept -> void - { - const result_type t = state[1] << 11; - - state[2] ^= state[0]; - state[5] ^= state[1]; - state[1] ^= state[2]; - state[7] ^= state[3]; - state[3] ^= state[4]; - state[4] ^= state[5]; - state[0] ^= state[6]; - state[6] ^= state[7]; - - state[6] ^= t; - rotate_left_to<21, engine_type::bits_of_this>(state[7]); - } - - /** - * @brief This is the jump function for the generator. It is equivalent - * to 2 ^ 256 calls to operator(); it can be used to generate 2 ^ 256 - * non-overlapping sub-sequences for parallel computations. - * @return generated jump steps - */ - [[nodiscard]] constexpr static auto jump() noexcept -> state_type - { - return { - static_cast(0x33ed89b6e7a353f9ull), - static_cast(0x760083d7955323beull), - static_cast(0x2837f2fbb5f22faeull), - static_cast(0x4b8c5674d309511cull), - static_cast(0xb11ac47a7ba28c25ull), - static_cast(0xf1be7667092bcc1cull), - static_cast(0x53851efdb6df0aafull), - static_cast(0x1ebbc8b23eaf25dbull) - }; - } - - /** - * @brief This is the long-jump function for the generator. It is equivalent to - * 2 ^ 384 calls to operator(); it can be used to generate 2 ^ 128 starting points, - * from each of which jump() will generate 2 ^ 128 non-overlapping - * sub-sequences for parallel distributed computations. - * @return generated long jump steps - */ - [[nodiscard]] constexpr static auto long_jump() noexcept -> state_type - { - return { - static_cast(0x11467fef8f921d28ull), - static_cast(0xa2a819f2e79c8ea8ull), - static_cast(0xa8299fc284b3959aull), - static_cast(0xb4d347340ca63ee1ull), - static_cast(0x1cb0940bedbff6ceull), - static_cast(0xd956c5c4fa1f8e17ull), - static_cast(0x915e38fd4eda93bcull), - static_cast(0x5b3ccdfa5d7daca5ull) - }; - } - }; - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - enum class RandomEngineCategory - { - // xor + shift + rotate - X_S_R, - // xor + rotate + shift + rotate - X_R_S_R, - }; - - enum class RandomEngineTag - { - PLUS, - PLUS_PLUS, - STAR_STAR, - }; - - enum class RandomEngineBit - { - BITS_128, - BITS_256, - BITS_512, - }; - - template - class RandomEngine; - - /** - * @brief Fastest 32-bit generator for 32-bit floating-point numbers. - * We suggest to use its upper bits for floating-point generation, - * if low linear complexity is not considered an issue (as it is usually - * the case) it can be used to generate 32-bit outputs, too. - * We suggest to use a sign test to extract a random Boolean value, and - * right shifts to extract subsets of bits. - * @note - * Output: 32 bits \n - * Period: 2 ^ 128 - 1 \n - * Footprint: 16 bytes \n - * @see http://prng.di.unimi.it/xoshiro128plus.c - */ - template<> - class RandomEngine - : public - random_engine_detail::RandomEngineBase< - random_engine_detail::bit32_type, - 4, - RandomEngine - > - { - friend RandomEngineBase; - - public: - using jumper = random_engine_detail::Jumper; - - using RandomEngineBase::RandomEngineBase; - - private: - [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type { return state_[0] + state_[3]; } - - constexpr auto do_next() noexcept -> result_type - { - const auto result = do_peek(); - jumper::rotate(state_); - return result; - } - - [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } - - [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } - }; - - using random_engine_xsr_128_plus = RandomEngine; - - static_assert(std::uniform_random_bit_generator); - static_assert(sizeof(random_engine_xsr_128_plus) == 128 / CHAR_BIT); - - /** - * @brief 32-bit all-purpose, rock-solid generators. - * It has excellent speed, a state size (128 bits) that is large enough for mild parallelism. - * @note - * Output: 32 bits \n - * Period: 2 ^ 128 - 1 \n - * Footprint: 16 bytes \n - * @see http://prng.di.unimi.it/xoshiro128plusplus.c - */ - template<> - class RandomEngine - : public - random_engine_detail::RandomEngineBase< - random_engine_detail::bit32_type, - 4, - RandomEngine - > - { - friend RandomEngineBase; - - public: - using jumper = random_engine_detail::Jumper; - - using RandomEngineBase::RandomEngineBase; - - private: - [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type - { - return random_engine_detail::rotate_left<7, bits_of_this>(state_[0] + state_[3]) + state_[0]; - } - - constexpr auto do_next() noexcept -> result_type - { - const auto result = do_peek(); - jumper::rotate(state_); - return result; - } - - [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } - - [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } - }; - - using random_engine_xsr_128_plus_plus = RandomEngine; - - static_assert(std::uniform_random_bit_generator); - static_assert(sizeof(random_engine_xsr_128_plus_plus) == 128 / CHAR_BIT); - - /** - * @brief 32-bit all-purpose, rock-solid generators. - * It has excellent speed, a state size (128 bits) that is large enough for mild parallelism. - * @note - * Output: 32 bits \n - * Period: 2 ^ 128 - 1 \n - * Footprint: 16 bytes \n - * @see http://prng.di.unimi.it/xoshiro128starstar.c - */ - template<> - class RandomEngine - : public - random_engine_detail::RandomEngineBase< - random_engine_detail::bit32_type, - 4, - RandomEngine - > - { - friend RandomEngineBase; - - public: - using jumper = random_engine_detail::Jumper; - - using RandomEngineBase::RandomEngineBase; - - private: - [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type - { - return random_engine_detail::rotate_left<7, bits_of_this>(state_[1] * 5) * 9; - } - - constexpr auto do_next() noexcept -> result_type - { - const auto result = do_peek(); - jumper::rotate(state_); - return result; - } - - [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } - - [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } - }; - - using random_engine_xsr_128_star_star = RandomEngine; - - static_assert(std::uniform_random_bit_generator); - static_assert(sizeof(random_engine_xsr_128_star_star) == 128 / CHAR_BIT); - - /** - * @brief Fastest small-state generator for floating-point numbers, - * but its state space is large enough only for mild parallelism. - * We suggest to use its upper bits for floating-point generation, - * if low linear complexity is not considered an issue (as it is usually - * the case) it can be used to generate 64-bit outputs, too. - * We suggest to use a sign test to extract a random Boolean value, and - * right shifts to extract subsets of bits. - * @note - * Output: 64 bits \n - * Period: 2 ^ 128 - 1 \n - * Footprint: 16 bytes \n - * @see http://prng.di.unimi.it/xoroshiro128plus.c - */ - template<> - class RandomEngine - : public - random_engine_detail::RandomEngineBase< - random_engine_detail::bit64_type, - 2, - RandomEngine - > - { - friend RandomEngineBase; - - public: - using jumper = random_engine_detail::Jumper; - - using RandomEngineBase::RandomEngineBase; - - private: - [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type { return state_[0] + state_[1]; } - - constexpr auto do_next() noexcept -> result_type - { - const auto result = do_peek(); - - const auto s1 = state_[1] ^ state_[0]; - - state_[0] = random_engine_detail::rotate_left<24, bits_of_this>(state_[0]) ^ s1 ^ (s1 << 16); - state_[1] = random_engine_detail::rotate_left<37, bits_of_this>(s1); - - return result; - } - - [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } - - [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } - }; - - using random_engine_xrsr_128_plus = RandomEngine; - - static_assert(std::uniform_random_bit_generator); - static_assert(sizeof(random_engine_xrsr_128_plus) == 128 / CHAR_BIT); - - /** - * @brief All-purpose, rock-solid, small-state generators, - * its state space is large enough only for mild parallelism. - * @note - * Output: 64 bits \n - * Period: 2 ^ 128 - 1 \n - * Footprint: 16 bytes \n - * @see http://prng.di.unimi.it/xoroshiro128plusplus.c - */ - template<> - class RandomEngine - : public - random_engine_detail::RandomEngineBase< - random_engine_detail::bit64_type, - 2, - RandomEngine - > - { - friend RandomEngineBase; - - public: - using jumper = random_engine_detail::Jumper; - - using RandomEngineBase::RandomEngineBase; - - private: - [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type - { - return random_engine_detail::rotate_left<17, bits_of_this>(state_[0] + state_[1]) + state_[0]; - } - - constexpr auto do_next() noexcept -> result_type - { - const auto result = do_peek(); - - const auto s1 = state_[1] ^ state_[0]; - - state_[0] = random_engine_detail::rotate_left<49, bits_of_this>(state_[0]) ^ s1 ^ (s1 << 21); - state_[1] = random_engine_detail::rotate_left<28, bits_of_this>(s1); - - return result; - } - - /** - * @brief This is the jump function for the generator. It is equivalent - * to 2 ^ 64 calls to operator(); it can be used to generate 2 ^ 64 - * non-overlapping sub-sequences for parallel computations. - * @return generated jump steps - */ - [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type - { - return { - static_cast(0x2bd7a6a6e99c2ddcull), - static_cast(0x0992ccaf6a6fca05ull) - }; - } - - /** - * @brief This is the long-jump function for the generator. It is equivalent to - * 2 ^ 96 calls to operator(); it can be used to generate 2 ^ 32 starting points, - * from each of which jump() will generate 2 ^ 32 non-overlapping - * sub-sequences for parallel distributed computations. - * @return generated jump steps - */ - [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type - { - return { - static_cast(0x360fd5f2cf8d5d99ull), - static_cast(0x9c6e6877736c46e3ull) - }; - } - }; - - using random_engine_xrsr_128_plus_plus = RandomEngine; - - static_assert(std::uniform_random_bit_generator); - static_assert(sizeof(random_engine_xrsr_128_plus_plus) == 128 / CHAR_BIT); - - /** - * @brief All-purpose, rock-solid, small-state generators, - * its state space is large enough only for mild parallelism. - * @note - * Output: 64 bits \n - * Period: 2 ^ 128 - 1 \n - * Footprint: 16 bytes \n - * @see http://prng.di.unimi.it/xoroshiro128starstar.c - */ - template<> - class RandomEngine - : public - random_engine_detail::RandomEngineBase< - random_engine_detail::bit64_type, - 2, - RandomEngine - > - { - friend RandomEngineBase; - - public: - using jumper = random_engine_detail::Jumper; - - using RandomEngineBase::RandomEngineBase; - - private: - [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type - { - return random_engine_detail::rotate_left<7, bits_of_this>(state_[0] * 5) * 9; - } - - constexpr auto do_next() noexcept -> result_type - { - const auto result = do_peek(); - - const auto s1 = state_[1] ^ state_[0]; - - state_[0] = random_engine_detail::rotate_left<24, bits_of_this>(state_[0]) ^ s1 ^ (s1 << 16); - state_[1] = random_engine_detail::rotate_left<37, bits_of_this>(s1); - - return result; - } - - [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } - - [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } - }; - - using random_engine_xrsr_128_star_star = RandomEngine; - - static_assert(std::uniform_random_bit_generator); - static_assert(sizeof(random_engine_xrsr_128_star_star) == 128 / CHAR_BIT); - - /** - * @brief Fastest generator for floating-point numbers. - * We suggest to use its upper bits for floating-point generation, - * if low linear complexity is not considered an issue (as it is usually the case) it - * can be used to generate 64-bit outputs, too. - * @note - * Output: 64 bits \n - * Period: 2 ^ 256 - 1 \n - * Footprint: 32 bytes \n - * @see http://prng.di.unimi.it/xoshiro256plus.c - */ - template<> - class RandomEngine - : public - random_engine_detail::RandomEngineBase< - random_engine_detail::bit64_type, - 4, - RandomEngine - > - { - friend RandomEngineBase; - - public: - using jumper = random_engine_detail::Jumper; - - using RandomEngineBase::RandomEngineBase; - - private: - [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type { return state_[0] + state_[3]; } - - constexpr auto do_next() noexcept -> result_type - { - const auto result = do_peek(); - jumper::rotate(state_); - return result; - } - - [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } - - [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } - }; - - using random_engine_xsr_256_plus = RandomEngine; - - static_assert(std::uniform_random_bit_generator); - static_assert(sizeof(random_engine_xsr_256_plus) == 256 / CHAR_BIT); - - /** - * @brief All-purpose, rock-solid generators, - * it has excellent (sub-ns) speed, a state (256 bits) that is large - * enough for any parallel application. - * @note - * Output: 64 bits \n - * Period: 2 ^ 256 - 1 \n - * Footprint: 32 bytes \n - * @see http://prng.di.unimi.it/xoshiro256plusplus.c - */ - template<> - class RandomEngine - : public - random_engine_detail::RandomEngineBase< - random_engine_detail::bit64_type, - 4, - RandomEngine - > - { - friend RandomEngineBase; - - public: - using jumper = random_engine_detail::Jumper; - - using RandomEngineBase::RandomEngineBase; - - private: - [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type - { - return random_engine_detail::rotate_left<23, bits_of_this>(state_[0] + state_[3]) + state_[0]; - } - - constexpr auto do_next() noexcept -> result_type - { - const auto result = do_peek(); - jumper::rotate(state_); - return result; - } - - [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } - - [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } - }; - - using random_engine_xsr_256_plus_plus = RandomEngine; - - static_assert(std::uniform_random_bit_generator); - static_assert(sizeof(random_engine_xsr_256_plus_plus) == 256 / CHAR_BIT); - - /** - * @brief All-purpose, rock-solid generators, - * it has excellent (sub-ns) speed, a state (256 bits) that is large - * enough for any parallel application. - * @note - * Output: 64 bits \n - * Period: 2 ^ 256 - 1 \n - * Footprint: 32 bytes \n - * @see http://prng.di.unimi.it/xoshiro256starstar.c - */ - template<> - class RandomEngine - : public - random_engine_detail::RandomEngineBase< - random_engine_detail::bit64_type, - 4, - RandomEngine - > - { - friend RandomEngineBase; - - public: - using jumper = random_engine_detail::Jumper; - - using RandomEngineBase::RandomEngineBase; - - private: - [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type - { - return random_engine_detail::rotate_left<7, bits_of_this>(state_[1] * 5) * 9; - } - - constexpr auto do_next() noexcept -> result_type - { - const auto result = do_peek(); - jumper::rotate(state_); - return result; - } - - [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } - - [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } - }; - - using random_engine_xsr_256_star_star = RandomEngine; - - static_assert(std::uniform_random_bit_generator); - static_assert(sizeof(random_engine_xsr_256_star_star) == 256 / CHAR_BIT); - - /** - * @brief Generator for floating-point numbers with increased state size. - * We suggest to use its upper bits for floating-point generation, - * if low linear complexity is not considered an issue (as it is usually the case) it - * can be used to generate 64-bit outputs, too. - * @note - * Output: 64 bits \n - * Period: 2 ^ 512 - 1 \n - * Footprint: 64 bytes \n - * @see http://prng.di.unimi.it/xoshiro512plus.c - */ - template<> - class RandomEngine - : public - random_engine_detail::RandomEngineBase< - random_engine_detail::bit64_type, - 8, - RandomEngine - > - { - friend RandomEngineBase; - - public: - using jumper = random_engine_detail::Jumper; - - using RandomEngineBase::RandomEngineBase; - - private: - [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type { return state_[0] + state_[3]; } - - constexpr auto do_next() noexcept -> result_type - { - const auto result = do_peek(); - jumper::rotate(state_); - return result; - } - - [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } - - [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } - }; - - using random_engine_xsr_512_plus = RandomEngine; - - static_assert(std::uniform_random_bit_generator); - static_assert(sizeof(random_engine_xsr_512_plus) == 512 / CHAR_BIT); - - /** - * @brief All-purpose, rock-solid generators, - * it has excellent (sub-ns) speed, a state (512 bits) that is large - * enough for any parallel application. - * @note - * Output: 64 bits \n - * Period: 2 ^ 512 - 1 \n - * Footprint: 64 bytes \n - * @see http://prng.di.unimi.it/xoshiro512plusplus.c - */ - template<> - class RandomEngine - : public - random_engine_detail::RandomEngineBase< - random_engine_detail::bit64_type, - 8, - RandomEngine - > - { - friend RandomEngineBase; - - public: - using jumper = random_engine_detail::Jumper; - - using RandomEngineBase::RandomEngineBase; - - private: - [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type - { - return random_engine_detail::rotate_left<17, bits_of_this>(state_[0] + state_[2]) + state_[2]; - } - - constexpr auto do_next() noexcept -> result_type - { - const auto result = do_peek(); - jumper::rotate(state_); - return result; - } - - [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } - - [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } - }; - - using random_engine_xsr_512_plus_plus = RandomEngine; - - static_assert(std::uniform_random_bit_generator); - static_assert(sizeof(random_engine_xsr_512_plus_plus) == 512 / CHAR_BIT); - - /** - * @brief All-purpose, rock-solid generators, - * it has excellent (sub-ns) speed, a state (512 bits) that is large - * enough for any parallel application. - * @note - * Output: 64 bits \n - * Period: 2 ^ 512 - 1 \n - * Footprint: 32 bytes \n - * @see http://prng.di.unimi.it/xoshiro512starstar.c - */ - template<> - class RandomEngine - : public - random_engine_detail::RandomEngineBase< - random_engine_detail::bit64_type, - 8, - RandomEngine - > - { - friend RandomEngineBase; - - public: - using jumper = random_engine_detail::Jumper; - - using RandomEngineBase::RandomEngineBase; - - private: - [[nodiscard]] constexpr auto do_peek() const noexcept -> result_type - { - return random_engine_detail::rotate_left<7, bits_of_this>(state_[1] * 5) * 9; - } - - constexpr auto do_next() noexcept -> result_type - { - const auto result = do_peek(); - jumper::rotate(state_); - return result; - } - - [[nodiscard]] constexpr static auto do_jump_state() noexcept -> state_type { return jumper::jump(); } - - [[nodiscard]] constexpr static auto do_long_jump_state() noexcept -> state_type { return jumper::long_jump(); } - }; - - using random_engine_xsr_512_star_star = RandomEngine; - - static_assert(std::uniform_random_bit_generator); - static_assert(sizeof(random_engine_xsr_512_star_star) == 512 / CHAR_BIT); - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -} diff --git a/src/error/instruction_set.impl.ixx b/src/platform/cpu.cpp similarity index 96% rename from src/error/instruction_set.impl.ixx rename to src/platform/cpu.cpp index bb9bc24e..a167f41d 100644 --- a/src/error/instruction_set.impl.ixx +++ b/src/platform/cpu.cpp @@ -1,30 +1,18 @@ // This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal +// Copyright (C) 2022-2025 Life4gal // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include -#if __has_include() -#include -#endif -#if __has_include() -#include -#endif - -export module gal.prometheus.error:instruction_set.impl; - -import std; - -import :instruction_set; - -#else +// @see /scripts/detect_supported_instruction.cpp #include #include #include + +#include + +#include + #if __has_include() #include #endif @@ -32,11 +20,6 @@ import :instruction_set; #include #endif -#include -#include - -#endif - namespace { // @see https://www.felixcloutier.com/x86/cpuid @@ -45,6 +28,10 @@ namespace // When CPUID executes with EAX set to 01H, feature information is returned in ECX and EDX. // ======================================= + // ReSharper disable CommentTypo + // ReSharper disable CppInconsistentNaming + // ReSharper disable IdentifierTypo + /** * [Figure 3-7](https://www.felixcloutier.com/x86/cpuid#fig-3-7). Feature Information Returned in the ECX Register * @@ -70,7 +57,7 @@ namespace * Bit 00: FSGSBASE. Supports RDFSBASE/RDGSBASE/WRFSBASE/WRGSBASE if 1. * Bit 01: IA32_TSC_ADJUST MSR is supported if 1. * Bit 02: SGX. Supports Intel® Software Guard Extensions (Intel® SGX Extensions) if 1. - * Bit 03: BMI1. + * Bit 03: BMI1. * Bit 04: HLE. * Bit 05: AVX2. Supports Intel® Advanced Vector Extensions 2 (Intel® AVX2) if 1. * Bit 06: FDP_EXCPTN_ONLY. x87 FPU Data Pointer updated only on x87 exceptions if 1. @@ -199,6 +186,10 @@ namespace constexpr auto avx512saved = std::uint64_t{0b1110'0000}; } + // ReSharper restore IdentifierTypo + // ReSharper restore CppInconsistentNaming + // ReSharper restore CommentTypo + struct cpu_id_t { std::uint32_t eax; @@ -219,7 +210,7 @@ namespace static_assert(sizeof(id_t) == sizeof(cpu_id_t)); static_assert(std::is_standard_layout_v); - id_t id; + id_t id{}; #if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) __cpuidex(reinterpret_cast(&id), static_cast(config.eax), static_cast(config.ecx)); #else @@ -249,7 +240,7 @@ namespace } } -namespace gal::prometheus::error +namespace gal::prometheus::platform { auto detect_supported_instruction() -> std::uint32_t { diff --git a/src/error/instruction_set.ixx b/src/platform/cpu.hpp similarity index 72% rename from src/error/instruction_set.ixx rename to src/platform/cpu.hpp index b03d2aaf..0dd14717 100644 --- a/src/error/instruction_set.ixx +++ b/src/platform/cpu.hpp @@ -1,30 +1,22 @@ // This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal +// Copyright (C) 2022-2025 Life4gal // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#if GAL_PROMETHEUS_USE_MODULE -module; +// @see /scripts/detect_supported_instruction.cpp -#include - -export module gal.prometheus.error:instruction_set; - -#else #pragma once #include #include -#endif - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::error) +namespace gal::prometheus::platform { // ReSharper disable CppInconsistentNaming // ReSharper disable IdentifierTypo - enum class InstructionSet : std::uint32_t + enum class InstructionSet : std::uint32_t // NOLINT(performance-enum-size) { DEFAULT = 0b0000'0000'0000'0000, @@ -46,4 +38,4 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::error) // ReSharper restore CppInconsistentNaming auto detect_supported_instruction() -> std::uint32_t; -} // namespace gal::prometheus::error +} diff --git a/src/platform/environment.cpp b/src/platform/environment.cpp new file mode 100644 index 00000000..a7198e85 --- /dev/null +++ b/src/platform/environment.cpp @@ -0,0 +1,44 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include + +#include + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +#if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) +#include +#endif + +namespace +{ + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + const int g_argc = *__p___argc(); + const char* const* g_argv = *__p___argv(); + #else + int g_argc = 0; + const char** g_argv = nullptr; + + __attribute__((constructor)) auto read_command_args(const int argc, const char* argv[]) -> void + { + g_argc = argc; + g_argv = argv; + } + #endif +} + +namespace gal::prometheus::platform +{ + auto command_args() noexcept -> std::span + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_argc != 0); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(g_argv != nullptr); + + return {g_argv, static_cast::size_type>(g_argc)}; + } +} diff --git a/src/platform/environment.hpp b/src/platform/environment.hpp new file mode 100644 index 00000000..6350087a --- /dev/null +++ b/src/platform/environment.hpp @@ -0,0 +1,15 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include + +namespace gal::prometheus::platform +{ + [[nodiscard]] auto command_args() noexcept -> std::span; +} diff --git a/src/platform/exception.cpp b/src/platform/exception.cpp new file mode 100644 index 00000000..5e215d41 --- /dev/null +++ b/src/platform/exception.cpp @@ -0,0 +1,46 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +// fixme +#if __has_include() +#include +#else +#include +#include +#endif + +#include + +namespace gal::prometheus::platform +{ + auto IException::print() const noexcept -> void + { + const auto& message = what(); + const auto& location = where(); + const auto& stacktrace = when(); + + #if __has_include() + std::println( + stderr, + "Error occurs while invoke function:\n{}\nat {}:{}\nReason:\n{}\nStack trace:\n{}", + location.function_name(), + location.file_name(), + location.line(), + message, + stacktrace + ); + #else + const auto output = std::format( + "Error occurs while invoke function:\n{}\nat {}:{}\nReason:\n{}\nStack trace:\n{}", + location.function_name(), + location.file_name(), + location.line(), + message, + stacktrace + ); + std::fputs(output.c_str(), stderr); + #endif + } +} diff --git a/src/platform/exception.hpp b/src/platform/exception.hpp new file mode 100644 index 00000000..dbb2aa06 --- /dev/null +++ b/src/platform/exception.hpp @@ -0,0 +1,297 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include + +#include + +// todo +#if (defined(GAL_PROMETHEUS_COMPILER_GNU) or defined(GAL_PROMETHEUS_COMPILER_CLANG)) and (not defined(_GLIBCXX_HAVE_STACKTRACE) or not _GLIBCXX_HAVE_STACKTRACE) +#include + +namespace std +{ + class stacktrace // NOLINT(*-dcl58-cpp) + { + public: + [[nodiscard]] static auto current() noexcept -> stacktrace + { + return {}; + } + + stacktrace() noexcept = default; + ~stacktrace() noexcept = default; + stacktrace(const stacktrace&) noexcept = default; + auto operator=(const stacktrace&) noexcept -> stacktrace& = default; + stacktrace(stacktrace&&) noexcept = default; + auto operator=(stacktrace&&) noexcept -> stacktrace& = default; + }; + + template<> + struct formatter // NOLINT(cert-dcl58-cpp) + { + template + constexpr auto parse(ParseContext& context) const noexcept -> auto + { + (void)this; + return context.begin(); + } + + template + auto format(const stacktrace&, FormatContext& context) const noexcept -> auto + { + return std::format_to(context.out(), "std::stacktrace not supported yet"); + } + }; +} +#else +#include +#endif + +namespace gal::prometheus::platform +{ + class IException + { + public: + constexpr IException() noexcept = default; + constexpr IException(const IException&) noexcept = default; + constexpr IException(IException&&) noexcept = default; + constexpr auto operator=(const IException&) noexcept -> IException& = default; + constexpr auto operator=(IException&&) noexcept -> IException& = default; + constexpr virtual ~IException() noexcept = default; + + [[nodiscard]] constexpr virtual auto what() const noexcept -> const std::string& = 0; + + [[nodiscard]] constexpr virtual auto where() const noexcept -> const std::source_location& = 0; + + [[nodiscard]] constexpr virtual auto when() const noexcept -> const std::stacktrace& = 0; + + auto print() const noexcept -> void; + }; + + template + // ReSharper disable once CppClassCanBeFinal + class Exception : public IException + { + public: + using data_type = T; + + private: + std::string message_; + std::source_location location_; + std::stacktrace stacktrace_; + data_type data_; + + public: + template + constexpr Exception( + StringType&& message, + DataType&& data, + const std::source_location location, + std::stacktrace stacktrace + ) noexcept + : IException{}, + message_{std::forward(message)}, + location_{location}, + stacktrace_{std::move(stacktrace)}, + data_{std::forward(data)} {} + + [[nodiscard]] constexpr auto what() const noexcept -> const std::string& override + { + return message_; + } + + [[nodiscard]] constexpr auto where() const noexcept -> const std::source_location& override + { + return location_; + } + + [[nodiscard]] constexpr auto when() const noexcept -> const std::stacktrace& override + { + return stacktrace_; + } + + [[nodiscard]] constexpr auto data() const noexcept -> data_type& + { + return data_; + } + }; + + template<> + // ReSharper disable once CppClassCanBeFinal + class Exception : public IException + { + public: + using data_type = void; + + private: + std::string message_; + std::source_location location_; + std::stacktrace stacktrace_; + + public: + template + constexpr Exception( + StringType&& message, + const std::source_location location, + std::stacktrace stacktrace + ) noexcept + : IException{}, + message_{std::forward(message)}, + location_{location}, + stacktrace_{std::move(stacktrace)} {} + + [[nodiscard]] constexpr auto what() const noexcept -> const std::string& override + { + return message_; + } + + [[nodiscard]] constexpr auto where() const noexcept -> const std::source_location& override + { + return location_; + } + + [[nodiscard]] constexpr auto when() const noexcept -> const std::stacktrace& override + { + return stacktrace_; + } + }; + + template + requires std::derived_from> + [[noreturn]] constexpr auto panic( + StringType&& message, + DataType&& data, + const std::source_location& location = std::source_location::current(), + std::stacktrace stacktrace = std::stacktrace::current() + ) noexcept(false) -> ExceptionType // + { + throw ExceptionType // NOLINT(hicpp-exception-baseclass) + { + std::forward(message), + std::forward(data), + location, + std::move(stacktrace) + }; + } + + template + requires std::derived_from> + [[noreturn]] constexpr auto panic( + StringType&& message, + const std::source_location& location = std::source_location::current(), + std::stacktrace stacktrace = std::stacktrace::current() + ) noexcept(false) -> ExceptionType // + { + throw ExceptionType // NOLINT(hicpp-exception-baseclass) + { + std::forward(message), + location, + std::move(stacktrace) + }; + } + + namespace exception_detail + { + template + struct panic_selector; + + template<> + struct panic_selector + { + template + requires ( + std::derived_from> and + std::is_same_v + ) + [[noreturn]] constexpr static auto invoke( + StringType&& message, + const std::source_location& location = std::source_location::current(), + std::stacktrace stacktrace = std::stacktrace::current() + ) noexcept(false) -> ExceptionType + { + ExceptionType::panic( + std::forward(message), + location, + std::move(stacktrace) + ); + GAL_PROMETHEUS_ERROR_UNREACHABLE(); + } + + template + requires ( + std::derived_from> and + not std::is_same_v + ) + [[noreturn]] constexpr static auto invoke( + StringType&& message, + typename ExceptionType::data_type data, + const std::source_location& location = std::source_location::current(), + std::stacktrace stacktrace = std::stacktrace::current() + ) noexcept(false) -> ExceptionType + { + ExceptionType::panic( + std::forward(message), + std::move(data), + location, + std::move(stacktrace) + ); + GAL_PROMETHEUS_ERROR_UNREACHABLE(); + } + }; + + template<> + struct panic_selector + { + template + requires ( + std::derived_from> and + std::is_same_v + ) + [[noreturn]] constexpr static auto invoke( + StringType&& message, + const std::source_location& location = std::source_location::current(), + std::stacktrace stacktrace = std::stacktrace::current() + ) noexcept(false) -> ExceptionType + { + platform::panic( + std::forward(message), + location, + std::move(stacktrace) + ); + } + + template + requires ( + std::derived_from> and + not std::is_same_v + ) + [[noreturn]] constexpr static auto invoke( + StringType&& message, + typename ExceptionType::data_type data, + const std::source_location& location = std::source_location::current(), + std::stacktrace stacktrace = std::stacktrace::current() + ) noexcept(false) -> ExceptionType + { + platform::panic( + std::forward(message), + std::move(data), + location, + std::move(stacktrace) + ); + } + }; + } + + template + using mob = exception_detail::panic_selector< + requires { ExceptionType::panic(std::declval()); } or + requires { ExceptionType::panic(std::declval(), std::declval()); } + >; +} diff --git a/src/platform/os.cpp b/src/platform/os.cpp new file mode 100644 index 00000000..5201b278 --- /dev/null +++ b/src/platform/os.cpp @@ -0,0 +1,118 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include +#include + +// fixme +#if __has_include() +#include +#else +#include +#include +#endif + +#include + +#if not defined(HAS_STD_DEBUGGING) +#if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) +#include +#elif defined(GAL_PROMETHEUS_PLATFORM_LINUX) +#include +#include +#elif defined(GAL_PROMETHEUS_PLATFORM_DARWIN) +#include +#include +#include +#else +#error "fixme" +#endif +#endif + +namespace gal::prometheus::platform +{ + auto os_error_reason() -> std::string + { + return std::system_category().message( + #if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) + static_cast(GetLastError()) + #else + errno + #endif + ); + } + + auto is_debugger_present() noexcept -> bool + { + #if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) + + if (IsDebuggerPresent() == TRUE) + { + return true; + } + + BOOL present = FALSE; + if (CheckRemoteDebuggerPresent(GetCurrentProcess(), &present) == TRUE) + { + return present == TRUE; + } + + return false; + + #elif defined(GAL_PROMETHEUS_PLATFORM_LINUX) + + // TracerPid: 0 + std::ifstream status_file("/proc/self/status"); + std::string line; + while (std::getline(status_file, line)) + { + if (line.starts_with("TracerPid")) + { + const std::string_view sub{line.begin() + sizeof("TracerPid:") - 1, line.end()}; + + if (const auto pid = string::from_string(sub); + pid.has_value() && *pid != 0) + { + return true; + } + } + } + + return false; + + #elif defined(GAL_PROMETHEUS_PLATFORM_DARWIN) + + int mib[4]; + struct kinfo_proc info; + size_t size = sizeof(info); + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + if (sysctl(mib, 4, &info, &size, NULL, 0) == -1) + { + return false; + } + + return (info.kp_proc.p_flag & P_TRACED) != 0; + + #else + #error "fixme" + #endif + } + + auto breakpoint_message(std::string_view message) noexcept -> void + { + #if __has_include() + std::println(stderr, "BREAKPOINT: {}", message); + #else + const auto output = std::format("BREAKPOINT: {}", message); + std::fputs(output.c_str(), stderr); + std::putc('\n', stderr); + #endif + } +} diff --git a/src/platform/os.hpp b/src/platform/os.hpp new file mode 100644 index 00000000..1d29211c --- /dev/null +++ b/src/platform/os.hpp @@ -0,0 +1,77 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include + +#if __has_include() +#include +#define HAS_STD_DEBUGGING +#endif + +#include + +#include + +namespace gal::prometheus::platform +{ + [[nodiscard]] auto os_error_reason() -> std::string; + + class OsError final : public Exception + { + public: + using Exception::Exception; + + [[noreturn]] static auto panic( + const std::source_location& location = std::source_location::current(), + std::stacktrace stacktrace = std::stacktrace::current() + ) noexcept(false) -> void // + { + platform::panic(os_error_reason(), location, std::move(stacktrace)); + } + }; + + [[nodiscard]] auto is_debugger_present() noexcept -> bool + #if defined(HAS_STD_DEBUGGING) + { + return std::is_debugger_present(); + } + #else + ; + #endif + + auto breakpoint_message(std::string_view message) noexcept -> void; + + #if defined(HAS_STD_DEBUGGING) + inline auto breakpoint_if_debugging(const std::string_view message) noexcept -> void + { + breakpoint_message(message); + std::breakpoint_if_debugging(); + } + #else + // use GAL_PROMETHEUS_ERROR_BREAKPOINT_IF + #endif + + #if defined(HAS_STD_DEBUGGING) + // inline auto breakpoint_or_terminate(const std::string_view message) noexcept -> void + // { + // breakpoint_message(message); + // if (is_debugger_present()) + // { + // GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); + // } + // else + // { + // std::terminate(); + // } + // } + #else + // use GAL_PROMETHEUS_ERROR_BREAKPOINT_OR_TERMINATE_IF + #endif +} diff --git a/src/platform/platform.hpp b/src/platform/platform.hpp new file mode 100644 index 00000000..418c7f72 --- /dev/null +++ b/src/platform/platform.hpp @@ -0,0 +1,11 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include diff --git a/src/primitive/circle.hpp b/src/primitive/circle.hpp new file mode 100644 index 00000000..b09c6ee7 --- /dev/null +++ b/src/primitive/circle.hpp @@ -0,0 +1,260 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace gal::prometheus +{ + namespace primitive + { + template + requires(std::is_arithmetic_v and std::is_arithmetic_v) + struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_circle final : meta::dimension> + { + using point_value_type = PointValueType; + using radius_value_type = RadiusValueType; + + using point_type = basic_point; + + template + requires (Index < 2) + using element_type = std::conditional_t; + + point_type point; + radius_value_type radius; + + // warning: missing initializer for member ‘gal::prometheus::primitive::basic_circle<2, float>::’ [-Wmissing-field-initializers] + // {.point = point, .radius = radius}; + // ^ + // No initialization value specified for base class `meta::dimension` + constexpr basic_circle() noexcept + : point{}, + radius{} {} + + constexpr basic_circle(const point_type point, const radius_value_type radius) noexcept + : point{point}, + radius{radius} {} + + template + requires(Index < 2) + [[nodiscard]] constexpr auto get() const noexcept -> const element_type& + { + if constexpr (Index == 0) { return point; } + else if constexpr (Index == 1) { return radius; } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template + requires(Index < 2) + [[nodiscard]] constexpr auto get() noexcept -> element_type& + { + if constexpr (Index == 0) { return point; } + else if constexpr (Index == 1) { return radius; } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + [[nodiscard]] constexpr auto center() const noexcept -> point_type + { + return point; + } + + [[nodiscard]] constexpr auto empty() const noexcept -> bool + { + return radius == 0; + } + + [[nodiscard]] constexpr auto valid() const noexcept -> bool + { + return radius >= 0; + } + + [[nodiscard]] constexpr auto includes(const point_type& p) const noexcept -> bool + { + return point.distance(p) <= radius; + } + + [[nodiscard]] constexpr auto includes(const basic_circle& circle) const noexcept -> bool + { + if (radius < circle.radius) { return false; } + + return point.distance(circle.center()) <= (radius - circle.radius); + } + }; + + template + [[nodiscard]] constexpr auto inscribed_rect( + const basic_circle& circle + ) noexcept -> basic_rect + { + const auto radius = [r = circle.radius] + { + if constexpr (std::is_floating_point_v) { return r * std::numbers::sqrt2_v; } + else if constexpr (sizeof(RadiusValueType) == sizeof(float)) { return static_cast(static_cast(r) * std::numbers::sqrt2_v); } + else { return static_cast(static_cast(r) * std::numbers::sqrt2_v); } + }(); + + using rect_type = basic_rect; + using extent_type = typename rect_type::extent_type; + + if constexpr (N == 2) + { + const auto extent = extent_type{radius, radius}; + const auto offset = extent / 2; + const auto left_top = circle.center - offset; + + return {.point = left_top, .extent = extent}; + } + else if constexpr (N == 3) + { + const auto extent = extent_type{radius, radius, radius}; + const auto offset = extent / 2; + const auto left_top_near = circle.center - offset; + + return {.point = left_top_near, .extent = extent}; + } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template + [[nodiscard]] constexpr auto circumscribed_rect( + const basic_circle& circle + ) noexcept -> basic_rect + { + using rect_type = basic_rect; + using point_type = typename rect_type::point_type; + using extent_type = typename rect_type::extent_type; + + if constexpr (N == 2) + { + const point_type left_top{circle.center.x - circle.radius, circle.center.y - circle.radius}; + const extent_type extent{circle.radius * 2, circle.radius * 2}; + + return {.point = left_top, .extent = extent}; + } + else if constexpr (N == 3) + { + const point_type left_top_near{circle.center.x - circle.radius, circle.center.y - circle.radius, circle.center.z - circle.radius}; + const extent_type extent{circle.radius * 2, circle.radius * 2, circle.radius * 2}; + + return {.point = left_top_near, .extent = extent}; + } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template + [[nodiscard]] constexpr auto inscribed_circle(const basic_rect& rect) noexcept -> basic_circle + { + if constexpr (N == 2) + { + const auto radius = std::ranges::min(rect.width(), rect.height()) / 2; + const auto center = rect.center(); + return {.point = center, .radius = radius}; + } + else if constexpr (N == 3) + { + const auto radius = std::ranges::min(rect.width(), rect.height(), rect.depth()) / 2; + const auto center = rect.center(); + return {.point = center, .radius = radius}; + } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template + [[nodiscard]] constexpr auto circumscribed_circle(const basic_rect& rect) noexcept -> basic_circle + { + using rect_type = basic_rect; + using point_type = typename rect_type::point_type; + + if constexpr (N == 2) + { + const auto radius = rect.size().template to().distance({0, 0}) / 2; + const auto center = rect.center(); + return {center, radius}; + } + else if constexpr (N == 3) + { + const auto radius = rect.size().template to().distance({0, 0, 0}) / 2; + const auto center = rect.center(); + return {center, radius}; + } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template + using basic_circle_2d = basic_circle<2, PointValueType, RadiusValueType>; + + template + using basic_circle_3d = basic_circle<3, PointValueType, RadiusValueType>; + } + + namespace meta + { + // This makes `circle1 == circle2` return a boolean instead of array + template + struct dimension_folder, DimensionFoldOperation::EQUAL> + { + constexpr static auto value = DimensionFoldCategory::ALL; + }; + + // This makes `circle1 != circle2` return a boolean instead of array + template + struct dimension_folder, DimensionFoldOperation::NOT_EQUAL> + { + constexpr static auto value = DimensionFoldCategory::ANY; + }; + } +} + +namespace std +{ + template + struct + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + [[msvc::known_semantics]] + #endif + tuple_element> // NOLINT(cert-dcl58-cpp) + { + using type = typename gal::prometheus::primitive::basic_circle::template element_type; + }; + + template + struct tuple_size> // NOLINT(cert-dcl58-cpp) + : std::integral_constant {}; + + template + struct formatter> // NOLINT(cert-dcl58-cpp) + { + template + constexpr auto parse(ParseContext& context) const noexcept -> auto + { + (void)this; + return context.begin(); + } + + template + auto format(const gal::prometheus::primitive::basic_circle& circle, FormatContext& context) const noexcept -> auto + { + return std::format_to( + context.out(), + "({}->{})", + circle.center, + circle.radius + ); + } + }; +} diff --git a/src/primitive/circle.ixx b/src/primitive/circle.ixx deleted file mode 100644 index 1d4efeaa..00000000 --- a/src/primitive/circle.ixx +++ /dev/null @@ -1,358 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.primitive:circle; - -import std; - -import :multidimensional; -import :point; -import :extent; -import :rect; - -#else -#pragma once - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#endif - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::primitive) -{ - template - requires (std::is_arithmetic_v and std::is_arithmetic_v) - struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_circle; - - template - struct is_basic_circle : std::false_type {}; - - template - struct is_basic_circle> : std::true_type {}; - - template - constexpr auto is_basic_circle_v = is_basic_circle::value; - - template - concept basic_circle_t = is_basic_circle_v; - - template - struct is_circle_compatible : std::false_type {}; - - template - struct is_circle_compatible : std::false_type {}; - - // point + radius - template> U> - requires(meta::member_size() == 2) - struct is_circle_compatible> : - std::bool_constant< - is_point_compatible_v, basic_point> and - std::convertible_to, RadiusType> - > {}; - - // X + Y + radius - template> U> - requires(meta::member_size() == 3) - struct is_circle_compatible> : - std::bool_constant< - std::convertible_to, PointValueType> and - std::convertible_to, PointValueType> and - std::convertible_to, RadiusType> - > {}; - - template - constexpr auto is_circle_compatible_v = is_circle_compatible::value; - - template - concept circle_compatible_t = is_circle_compatible_v; - - template - [[nodiscard]] constexpr auto operator==( - const basic_circle& lhs, - const basic_circle& rhs - ) noexcept -> bool - { - return lhs.center == rhs.center and lhs.radius == rhs.radius; - } - - template> R> - [[nodiscard]] constexpr auto operator==(const basic_circle& lhs, const R& rhs) noexcept -> bool - { - if constexpr (meta::member_size() == 2) - { - // point + radius - return lhs.center == meta::member_of_index<0>(rhs) and lhs.radius == meta::member_of_index<1>(rhs); - } - else if constexpr (meta::member_size() == 3) - { - // X + Y + radius - const auto x = meta::member_of_index<0>(rhs); - const auto y = meta::member_of_index<1>(rhs); - const auto radius = meta::member_of_index<2>(rhs); - - return x == lhs.center.x and y == lhs.center.y and radius == lhs.radius; - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - } - - template> L> - [[nodiscard]] constexpr auto operator==(const L& lhs, const basic_circle& rhs) noexcept -> bool - { - return rhs == lhs; - } - - template - requires(std::is_arithmetic_v and std::is_arithmetic_v) - struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_circle final : multidimensional, PointValueType, RadiusType> - { - using point_value_type = PointValueType; - using radius_type = RadiusType; - - using point_type = basic_point; - - constexpr static auto is_always_equal = true; - - constexpr static std::size_t element_size{2}; - template - requires(Index < element_size) - using element_type = std::conditional_t; - - point_type center; - radius_type radius; - - constexpr explicit(false) basic_circle(const point_value_type point_value = point_value_type{0}, const radius_type radius_value = radius_type{0}) noexcept - : center{point_value}, - radius{radius_value} {} - - constexpr basic_circle(const point_value_type x, const point_value_type y, const radius_type radius_value) noexcept - : center{x, y}, - radius{radius_value} {} - - constexpr basic_circle(const point_type& point_value, const radius_type radius_value) noexcept - : center{point_value}, - radius{radius_value} {} - - template U> - constexpr explicit basic_circle(const U& value) noexcept - : basic_circle{} - { - *this = value; - } - - constexpr basic_circle(const basic_circle&) noexcept = default; - constexpr basic_circle(basic_circle&&) noexcept = default; - constexpr auto operator=(const basic_circle&) noexcept -> basic_circle& = default; - constexpr auto operator=(basic_circle&&) noexcept -> basic_circle& = default; - constexpr ~basic_circle() noexcept = default; - - template U> - constexpr auto operator=(const U& value) noexcept -> basic_circle& - { - const auto [_center, _radius] = value; - center = static_cast(_center); - radius = static_cast(_radius); - - return *this; - } - - template - requires(Index < 2) - [[nodiscard]] constexpr auto get() const noexcept -> std::add_lvalue_reference_t>> - { - if constexpr (Index == 0) { return center; } - else if constexpr (Index == 1) { return radius; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template - requires(Index < 2) - [[nodiscard]] constexpr auto get() noexcept -> std::add_lvalue_reference_t> - { - if constexpr (Index == 0) { return center; } - else if constexpr (Index == 1) { return radius; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - [[nodiscard]] constexpr auto empty() const noexcept -> bool { return radius == 0; } - - [[nodiscard]] constexpr auto includes(const point_type& point) const noexcept -> bool // - { - return center.distance(point) <= radius; - } - - [[nodiscard]] constexpr auto includes(const basic_circle& circle) const noexcept -> bool - { - if (radius < circle.radius) { return false; } - - return center.distance(circle.center) <= (radius - circle.radius); - } - }; - - template - using basic_circle_2d = basic_circle<2, PointValueType, RadiusType>; - - template - using basic_circle_3d = basic_circle<3, PointValueType, RadiusType>; - - template - [[nodiscard]] constexpr auto inscribed_rect(const basic_circle& circle) noexcept -> basic_rect - { - const auto size = [r = circle.radius] - { - if constexpr (std::is_floating_point_v) { return r * std::numbers::sqrt2_v; } - else if constexpr (sizeof(RadiusType) == sizeof(float)) { return static_cast(static_cast(r) * std::numbers::sqrt2_v); } - else { return static_cast(static_cast(r) * std::numbers::sqrt2_v); } - }(); - - if constexpr (N == 2) - { - const auto extent = typename basic_rect::extent_type{size, size}; - const auto offset = extent / 2; - const auto left_top = circle.center - offset; - - return {left_top, extent}; - } - else if constexpr (N == 3) - { - const auto extent = typename basic_rect::extent_type{size, size, size}; - const auto offset = extent / 2; - const auto left_top_near = circle.center - offset; - - return {left_top_near, extent}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template - [[nodiscard]] constexpr auto circumscribed_rect(const basic_circle& circle) noexcept -> basic_rect - { - if constexpr (N == 2) - { - return - { - // left - circle.center.x - circle.radius, - // top - circle.center.y - circle.radius, - // right - circle.center.x + circle.radius, - // bottom - circle.center.y + circle.radius - }; - } - else if constexpr (N == 3) - { - return - { - // left - circle.center.x - circle.radius, - // top - circle.center.y - circle.radius, - // near - circle.center.z - circle.radius, - // right - circle.center.x + circle.radius, - // bottom - circle.center.y + circle.radius, - // far - circle.center.z + circle.radius - }; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template - [[nodiscard]] constexpr auto inscribed_circle(const basic_rect& rect) noexcept -> basic_circle - { - if constexpr (N == 2) - { - const auto radius = std::ranges::min(rect.width(), rect.height()) / 2; - const auto center = rect.center(); - return {center, radius}; - } - else if constexpr (N == 3) - { - const auto radius = std::ranges::min(rect.width(), rect.height(), rect.depth()) / 2; - const auto center = rect.center(); - return {center, radius}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template - [[nodiscard]] constexpr auto circumscribed_circle(const basic_rect& rect) noexcept -> basic_circle - { - if constexpr (N == 2) - { - const auto radius = rect.size().template to::point_type>().distance({0, 0}) / 2; - const auto center = rect.center(); - return {center, radius}; - } - else if constexpr (N == 3) - { - const auto radius = rect.size().template to::point_type>().distance({0, 0, 0}) / 2; - const auto center = rect.center(); - return {center, radius}; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } -} - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE_STD -{ - template - struct - #if defined(GAL_PROMETHEUS_COMPILER_MSVC) - [[msvc::known_semantics]] - #endif - tuple_element> // NOLINT(cert-dcl58-cpp) - { - using type = typename gal::prometheus::primitive::basic_circle::template element_type; - }; - - template - struct tuple_size> // NOLINT(cert-dcl58-cpp) - : std::integral_constant {}; - - template - struct formatter> // NOLINT(cert-dcl58-cpp) - { - template - constexpr auto parse(ParseContext& context) const noexcept -> auto - { - (void)this; - return context.begin(); - } - - template - auto format(const gal::prometheus::primitive::basic_circle& circle, FormatContext& context) const noexcept -> auto - { - return std::format_to( - context.out(), - "[{}->{}]", - circle.center, - circle.radius - ); - } - }; -} diff --git a/src/primitive/color.ixx b/src/primitive/color.hpp similarity index 51% rename from src/primitive/color.ixx rename to src/primitive/color.hpp index 113ba234..922489ca 100644 --- a/src/primitive/color.ixx +++ b/src/primitive/color.hpp @@ -1,19 +1,8 @@ // This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal +// Copyright (C) 2022-2025 Life4gal // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.primitive:color; - -import std; -import :multidimensional; - -#else #pragma once #include @@ -21,15 +10,10 @@ import :multidimensional; #include #include -#include -#endif - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::primitive) +namespace gal::prometheus::primitive { - using universal_32_bit_color_type = std::uint32_t; - - enum class ColorFormat + enum class ColorFormat : std::uint8_t { // red + green + blue R_G_B, @@ -45,249 +29,182 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::primitive) A_B_G_R, }; - template - struct color_format_type {}; + namespace color_detail + { + template + struct color_format_type {}; + } template - constexpr auto color_format = color_format_type{}; - - template - requires std::is_arithmetic_v - struct basic_color; - - template - struct is_basic_color : std::false_type {}; - - template - struct is_basic_color> : std::true_type {}; + constexpr auto color_format = color_detail::color_format_type{}; - template - constexpr auto is_basic_color_v = is_basic_color::value; - - template - concept basic_color_t = is_basic_color_v; + using universal_32_bit_color_type = std::uint32_t; - template - requires std::is_arithmetic_v - struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_color final : multidimensional, T> + struct [[nodiscard]] basic_color final { - using value_type = T; - constexpr static auto is_integral_value = std::is_integral_v; + using value_type = std::uint8_t; value_type red; value_type green; value_type blue; value_type alpha; - constexpr explicit(false) basic_color(const value_type value = value_type{0}) noexcept - : red{value}, - green{value}, - blue{value}, - alpha{0} {} - - constexpr basic_color(const value_type red, const value_type green, const value_type blue, const value_type alpha) noexcept - : red{red}, - green{green}, - blue{blue}, - alpha{alpha} {} - - template - constexpr basic_color(const universal_32_bit_color_type color, const color_format_type) noexcept - : basic_color{} + template + constexpr static auto from(const universal_32_bit_color_type color, const color_detail::color_format_type = {}) noexcept -> basic_color { - *this = [color]() noexcept -> basic_color + const auto to_value = [](const auto value) noexcept -> value_type { - constexpr auto to_value = [](const auto v) noexcept - { - if constexpr (is_integral_value) - { - return static_cast(v); - } - else - { - return static_cast(v) / static_cast(255); - } - }; + return static_cast(value & 0xff); + }; - if constexpr (Format == ColorFormat::R_G_B) + if constexpr (Format == ColorFormat::R_G_B) + { + return { - return - { - // red - to_value((color >> 16) & 0xff), - // green - to_value((color >> 8) & 0xff), - // blue - to_value((color >> 0) & 0xff), - // alpha - to_value(0xff) - }; - } - else if constexpr (Format == ColorFormat::R_G_B_A) + .red = to_value(color >> 16), + .green = to_value(color >> 8), + .blue = to_value(color >> 0), + .alpha = to_value(0xff) + }; + } + else if constexpr (Format == ColorFormat::R_G_B_A) + { + return { - return - { - // red - to_value((color >> 24) & 0xff), - // green - to_value((color >> 16) & 0xff), - // blue - to_value((color >> 8) & 0xff), - // alpha - to_value((color >> 0) & 0xff) - }; - } - else if constexpr (Format == ColorFormat::A_R_G_B) + .red = to_value(color >> 24), + .green = to_value(color >> 16), + .blue = to_value(color >> 8), + .alpha = to_value(color >> 0) + }; + } + else if constexpr (Format == ColorFormat::A_R_G_B) + { + return { - return - { - // red - to_value((color >> 16) & 0xff), - // green - to_value((color >> 8) & 0xff), - // blue - to_value((color >> 0) & 0xff), - // alpha - to_value((color >> 24) & 0xff) - }; - } - else if constexpr (Format == ColorFormat::B_G_R) + .red = to_value(color >> 16), + .green = to_value(color >> 8), + .blue = to_value(color >> 0), + .alpha = to_value(color >> 24) + }; + } + else if constexpr (Format == ColorFormat::B_G_R) + { + return { - return - { - // red - to_value((color >> 0) & 0xff), - // green - to_value((color >> 8) & 0xff), - // blue - to_value((color >> 16) & 0xff), - // alpha - to_value(0xff) - }; - } - else if constexpr (Format == ColorFormat::B_G_R_A) + .red = to_value(color >> 0), + .green = to_value(color >> 8), + .blue = to_value(color >> 16), + .alpha = to_value(0xff) + }; + } + else if constexpr (Format == ColorFormat::B_G_R_A) + { + return { - return - { - // red - to_value((color >> 8) & 0xff), - // green - to_value((color >> 16) & 0xff), - // blue - to_value((color >> 24) & 0xff), - // alpha - to_value((color >> 0) & 0xff) - }; - } - else if constexpr (Format == ColorFormat::A_B_G_R) + .red = to_value(color >> 8), + .green = to_value(color >> 16), + .blue = to_value(color >> 24), + .alpha = to_value(color >> 0) + }; + } + else if constexpr (Format == ColorFormat::A_B_G_R) + { + return { - return - { - // red - to_value((color >> 0) & 0xff), - // green - to_value((color >> 8) & 0xff), - // blue - to_value((color >> 16) & 0xff), - // alpha - to_value((color >> 24) & 0xff) - }; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - }(); + .red = to_value(color >> 0), + .green = to_value(color >> 8), + .blue = to_value(color >> 16), + .alpha = to_value(color >> 24) + }; + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } } [[nodiscard]] constexpr auto transparent() const noexcept -> basic_color { - return { - // red - red, - // green - green, - // blue - blue, - // alpha - 0 + return + { + .red = red, + .green = green, + .blue = blue, + .alpha = 0 }; } - template - [[nodiscard]] constexpr auto to() const noexcept -> universal_32_bit_color_type + template + [[nodiscard]] constexpr auto to(const color_detail::color_format_type = {}) const noexcept -> universal_32_bit_color_type { - constexpr auto to_value = [](const auto v) noexcept + const auto to_value = [](const auto value) noexcept -> universal_32_bit_color_type { - if constexpr (is_integral_value) - { - return static_cast(v); - } - else - { - return static_cast(v * 255); - } + return static_cast(value); }; if constexpr (Format == ColorFormat::R_G_B) { return - ((to_value(red) & 0xff) << 16) | - ((to_value(green) & 0xff) << 8) | - ((to_value(blue) & 0xff) << 0); + to_value(red << 16) | + to_value(green << 8) | + to_value(blue << 0); } else if constexpr (Format == ColorFormat::R_G_B_A) { return - ((to_value(red) & 0xff) << 24) | - ((to_value(green) & 0xff) << 16) | - ((to_value(blue) & 0xff) << 8) | - ((to_value(alpha) & 0xff) << 0); + to_value(red << 24) | + to_value(green << 16) | + to_value(blue << 8) | + to_value(alpha << 0); } else if constexpr (Format == ColorFormat::A_R_G_B) { return - ((to_value(red) & 0xff) << 16) | - ((to_value(green) & 0xff) << 8) | - ((to_value(blue) & 0xff) << 0) | - ((to_value(alpha) & 0xff) << 24); + to_value(red << 16) | + to_value(green << 8) | + to_value(blue << 0) | + to_value(alpha << 24); } else if constexpr (Format == ColorFormat::B_G_R) { return - ((to_value(red) & 0xff) << 0) | - ((to_value(green) & 0xff) << 8) | - ((to_value(blue) & 0xff) << 16); + to_value(red << 0) | + to_value(green << 8) | + to_value(blue << 16); } else if constexpr (Format == ColorFormat::B_G_R_A) { return - ((to_value(red) & 0xff) << 8) | - ((to_value(green) & 0xff) << 16) | - ((to_value(blue) & 0xff) << 24) | - ((to_value(alpha) & 0xff) << 0); + to_value(red << 8) | + to_value(green << 16) | + to_value(blue << 24) | + to_value(alpha << 0); } else if constexpr (Format == ColorFormat::A_B_G_R) { return - ((to_value(red) & 0xff) << 0) | - ((to_value(green) & 0xff) << 8) | - ((to_value(blue) & 0xff) << 16) | - ((to_value(alpha) & 0xff) << 24); + to_value(red << 0) | + to_value(green << 8) | + to_value(blue << 16) | + to_value(alpha << 24); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template - [[nodiscard]] constexpr auto to(const color_format_type) const noexcept -> universal_32_bit_color_type // - { - return this->to(); } }; + static_assert(sizeof(basic_color) == sizeof(universal_32_bit_color_type)); + namespace colors { - using color_type = basic_color; - constexpr auto build_color = [](const std::uint8_t red, const std::uint8_t green, const std::uint8_t blue) noexcept -> color_type + using color_type = basic_color; + using value_type = color_type::value_type; + + constexpr auto build_color = [](const value_type red, const value_type green, const value_type blue) noexcept -> basic_color { - return {red, green, blue, 0xff}; + return {.red = red, .green = green, .blue = blue, .alpha = 0xff}; }; constexpr color_type alice_blue = build_color(240, 248, 255); @@ -422,132 +339,27 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::primitive) constexpr color_type white_smoke = build_color(245, 245, 245); constexpr color_type yellow = build_color(255, 255, 0); constexpr color_type yellow_green = build_color(50, 216, 56); - - constexpr color_type gray[] = - { - build_color(0, 0, 0), - build_color(3, 3, 3), - build_color(5, 5, 5), - build_color(8, 8, 8), - build_color(10, 10, 10), - build_color(13, 13, 13), - build_color(15, 15, 15), - build_color(18, 18, 18), - build_color(20, 20, 20), - build_color(23, 23, 23), - build_color(26, 26, 26), - build_color(28, 28, 28), - build_color(31, 31, 31), - build_color(33, 33, 33), - build_color(36, 36, 36), - build_color(38, 38, 38), - build_color(41, 41, 41), - build_color(43, 43, 43), - build_color(46, 46, 46), - build_color(48, 48, 48), - build_color(51, 51, 51), - build_color(54, 54, 54), - build_color(56, 56, 56), - build_color(59, 59, 59), - build_color(61, 61, 61), - build_color(64, 64, 64), - build_color(66, 66, 66), - build_color(69, 69, 69), - build_color(71, 71, 71), - build_color(74, 74, 74), - build_color(77, 77, 77), - build_color(79, 79, 79), - build_color(82, 82, 82), - build_color(84, 84, 84), - build_color(87, 87, 87), - build_color(89, 89, 89), - build_color(92, 92, 92), - build_color(94, 94, 94), - build_color(97, 97, 97), - build_color(99, 99, 99), - build_color(102, 102, 102), - build_color(105, 105, 105), - build_color(107, 107, 107), - build_color(110, 110, 110), - build_color(112, 112, 112), - build_color(115, 115, 115), - build_color(117, 117, 117), - build_color(120, 120, 120), - build_color(122, 122, 122), - build_color(125, 125, 125), - build_color(127, 127, 127), - build_color(130, 130, 130), - build_color(133, 133, 133), - build_color(135, 135, 135), - build_color(138, 138, 138), - build_color(140, 140, 140), - build_color(143, 143, 143), - build_color(145, 145, 145), - build_color(148, 148, 148), - build_color(150, 150, 150), - build_color(153, 153, 153), - build_color(156, 156, 156), - build_color(158, 158, 158), - build_color(161, 161, 161), - build_color(163, 163, 163), - build_color(166, 166, 166), - build_color(168, 168, 168), - build_color(171, 171, 171), - build_color(173, 173, 173), - build_color(176, 176, 176), - build_color(179, 179, 179), - build_color(181, 181, 181), - build_color(184, 184, 184), - build_color(186, 186, 186), - build_color(189, 189, 189), - build_color(191, 191, 191), - build_color(194, 194, 194), - build_color(196, 196, 196), - build_color(199, 199, 199), - build_color(201, 201, 201), - build_color(204, 204, 204), - build_color(207, 207, 207), - build_color(209, 209, 209), - build_color(212, 212, 212), - build_color(214, 214, 214), - build_color(217, 217, 217), - build_color(219, 219, 219), - build_color(222, 222, 222), - build_color(224, 224, 224), - build_color(227, 227, 227), - build_color(229, 229, 229), - build_color(232, 232, 232), - build_color(235, 235, 235), - build_color(237, 237, 237), - build_color(240, 240, 240), - build_color(242, 242, 242), - build_color(245, 245, 245), - build_color(247, 247, 247), - build_color(250, 250, 250), - build_color(252, 252, 252), - build_color(255, 255, 255), - }; } } -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE_STD +namespace std { - template + template struct #if defined(GAL_PROMETHEUS_COMPILER_MSVC) [[msvc::known_semantics]] #endif - tuple_element> // NOLINT(cert-dcl58-cpp) + tuple_element // NOLINT(cert-dcl58-cpp) { - using type = T; + using type = gal::prometheus::primitive::basic_color::value_type; }; - template - struct tuple_size> // NOLINT(cert-dcl58-cpp) + template<> + struct tuple_size // NOLINT(cert-dcl58-cpp) : std::integral_constant {}; - template - struct formatter> // NOLINT(cert-dcl58-cpp) + template<> + struct formatter // NOLINT(cert-dcl58-cpp) { template constexpr auto parse(ParseContext& context) const noexcept -> auto @@ -557,13 +369,13 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE_STD } template - auto format(const gal::prometheus::primitive::basic_color& color, FormatContext& context) const noexcept -> auto + auto format(const gal::prometheus::primitive::basic_color& color, FormatContext& context) const noexcept -> auto { return std::format_to( context.out(), "#{:x}", - color.template to() + color.to() ); } }; -} +}; diff --git a/src/primitive/ellipse.hpp b/src/primitive/ellipse.hpp new file mode 100644 index 00000000..baaa698a --- /dev/null +++ b/src/primitive/ellipse.hpp @@ -0,0 +1,262 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace gal::prometheus +{ + namespace primitive + { + template + struct basic_ellipse; + + // 2D + template + requires(std::is_arithmetic_v and std::is_arithmetic_v and std::is_arithmetic_v) + struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_ellipse<2, PointValueType, RadiusValueType, RotationValueType> final + : meta::dimension> + { + using point_value_type = PointValueType; + using radius_value_type = RadiusValueType; + using rotation_value_type = RotationValueType; + + using point_type = basic_point<2, point_value_type>; + using radius_type = basic_extent<2, radius_value_type>; + + template + requires (Index < 3) + using element_type = std::conditional_t>; + + point_type point; + radius_type radius; + // multiples of PI + // e.q. .5f * std::numbers::pi_v + rotation_value_type rotation; + + // warning: missing initializer for member ‘gal::prometheus::primitive::basic_ellipse<2, float>::’ [-Wmissing-field-initializers] + // {.point = point, .radius = radius, .rotation = rotation}; + // ^ + // No initialization value specified for base class `meta::dimension` + constexpr basic_ellipse() noexcept + : point{}, + radius{radius}, + rotation{} {} + + constexpr basic_ellipse(const point_type point, const radius_type radius, const rotation_value_type rotation = rotation_value_type{0}) noexcept + : point{point}, + radius{radius}, + rotation{rotation} {} + + template + requires(Index < 3) + [[nodiscard]] constexpr auto get() const noexcept -> const element_type& + { + if constexpr (Index == 0) { return point; } + else if constexpr (Index == 1) { return radius; } + else if constexpr (Index == 2) { return rotation; } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template + requires(Index < 3) + [[nodiscard]] constexpr auto get() noexcept -> element_type& + { + if constexpr (Index == 0) { return point; } + else if constexpr (Index == 1) { return radius; } + else if constexpr (Index == 2) { return rotation; } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + [[nodiscard]] constexpr auto center() const noexcept -> point_type + { + return point; + } + + [[nodiscard]] constexpr auto empty() const noexcept -> bool + { + return radius.width == 0 and radius.height == 0; + } + + [[nodiscard]] constexpr auto valid() const noexcept -> bool + { + return radius.width >= 0 and radius.height >= 0; + } + + [[nodiscard]] constexpr auto includes(const point_type& p) const noexcept -> bool // + { + const auto dx = static_cast(p.x - point.x); + const auto dy = static_cast(p.y - point.y); + + if (rotation == 0) // NOLINT(clang-diagnostic-float-equal) + { + return + ( + math::pow(dx, 2) / static_cast(math::pow(radius.width, 2)) + + math::pow(dy, 2) / static_cast(math::pow(radius.height, 2)) + ) + <= static_cast(1); + } + + // Rotate point back by ellipse's rotation + const auto cos_theta = math::cos(rotation); + const auto sin_theta = math::sin(rotation); + const auto prime_x = dx * cos_theta + dy * sin_theta; + const auto prime_y = -dx * sin_theta + dy * cos_theta; + + // Check if the point is inside the ellipse's standard form + return + ( + math::pow(prime_x, 2) / static_cast(math::pow(radius.width, 2)) + + math::pow(prime_y, 2) / static_cast(math::pow(radius.height, 2)) + ) + <= static_cast(1); + } + + [[nodiscard]] constexpr auto includes(const basic_circle<2, point_value_type, radius_value_type>& circle) const noexcept -> bool + { + const auto dx = static_cast(circle.point.x - point.x); + const auto dy = static_cast(circle.point.y - point.y); + + // Scale the circle's radius to ellipse's coordinate space + const auto scaled_radius_x = static_cast(circle.radius) / static_cast(radius.width); + const auto scaled_radius_y = static_cast(circle.radius) / static_cast(radius.height); + + if (rotation == 0) // NOLINT(clang-diagnostic-float-equal) + { + const auto distance = + math::hypot( + dx / static_cast(radius.width), + dy / static_cast(radius.height) + ); + return distance + std::ranges::max(scaled_radius_x, scaled_radius_y) <= static_cast(1); + } + + // Rotate point back by ellipse's rotation + const auto cos_theta = math::cos(rotation); + const auto sin_theta = math::sin(rotation); + const auto prime_x = dx * cos_theta + dy * sin_theta; + const auto prime_y = -dx * sin_theta + dy * cos_theta; + + // Check if the transformed circle is inside the unit circle + const auto distance = + math::hypot( + prime_x / static_cast(radius.width), + prime_y / static_cast(radius.height) + ); + return distance + std::ranges::max(scaled_radius_x, scaled_radius_y) <= static_cast(1); + } + + [[nodiscard]] constexpr auto includes(const basic_ellipse& ellipse) const noexcept -> bool + { + const auto dx = static_cast(ellipse.point.x - point.x); + const auto dy = static_cast(ellipse.point.y - point.y); + + // Scale the ellipse's radius to this ellipse's coordinate space + const auto scaled_rx = static_cast(ellipse.radius.width) / static_cast(radius.width); + const auto scaled_ry = static_cast(ellipse.radius.height) / static_cast(radius.height); + + if (rotation == 0) // NOLINT(clang-diagnostic-float-equal) + { + const auto distance = + math::hypot( + dx / static_cast(radius.width), + dy / static_cast(radius.height) + ); + return distance + std::ranges::max(scaled_rx, scaled_ry) <= static_cast(1); + } + + // Rotate point back by this ellipse's rotation + const auto cos_theta = math::cos(rotation); + const auto sin_theta = math::sin(rotation); + const auto prime_x = dx * cos_theta + dy * sin_theta; + const auto prime_y = -dx * sin_theta + dy * cos_theta; + + // Check if the transformed ellipse is inside the unit circle + const auto distance = + math::hypot( + prime_x / static_cast(radius.width), + prime_y / static_cast(radius.height) + ); + return distance + std::ranges::max(scaled_rx, scaled_ry) <= static_cast(1); + } + }; + + template + using basic_ellipse_2d = basic_ellipse<2, PointValueType, RadiusValueType, RotationValueType>; + } + + namespace meta + { + // This makes `ellipse1 == ellipse2` return a boolean instead of array + template + struct dimension_folder, DimensionFoldOperation::EQUAL> + { + constexpr static auto value = DimensionFoldCategory::ALL; + }; + + // This makes `ellipse1 != ellipse2` return a boolean instead of array + template + struct dimension_folder, DimensionFoldOperation::NOT_EQUAL> + { + constexpr static auto value = DimensionFoldCategory::ANY; + }; + } +} + +namespace std +{ + template + struct + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + [[msvc::known_semantics]] + #endif + tuple_element> // NOLINT(cert-dcl58-cpp) + { + using type = typename gal::prometheus::primitive::basic_ellipse::template element_type; + }; + + template + struct tuple_size> // NOLINT(cert-dcl58-cpp) + : std::integral_constant {}; + + template + struct formatter> // NOLINT(cert-dcl58-cpp) + { + template + constexpr auto parse(ParseContext& context) const noexcept -> auto + { + (void)this; + return context.begin(); + } + + template + auto format(const gal::prometheus::primitive::basic_ellipse& ellipse, FormatContext& context) const noexcept -> auto + { + return std::format_to( + context.out(), + "({}->{}[{} °])", + ellipse.center, + ellipse.radius, + ellipse.rotation * 180 + ); + } + }; +} diff --git a/src/primitive/ellipse.ixx b/src/primitive/ellipse.ixx deleted file mode 100644 index e37549b3..00000000 --- a/src/primitive/ellipse.ixx +++ /dev/null @@ -1,406 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.primitive:ellipse; - -import std; -import gal.prometheus.functional; - -import :multidimensional; -import :point; -import :extent; -import :rect; - -#else -#pragma once - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#endif - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::primitive) -{ - template - requires(std::is_arithmetic_v and std::is_arithmetic_v and std::is_arithmetic_v) - struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_ellipse; - - template - struct is_basic_ellipse : std::false_type {}; - - template - struct is_basic_ellipse> : std::true_type {}; - - template - constexpr auto is_basic_ellipse_v = is_basic_ellipse::value; - - template - concept basic_ellipse_t = is_basic_ellipse_v; - - template - struct is_ellipse_compatible : std::false_type {}; - - template - struct is_ellipse_compatible : std::false_type {}; - - // point + radius - template< - std::size_t N, - typename PointValueType, - typename RadiusValueType, - typename RotationValueType, - member_gettable_but_not_same_t> U - > - requires(meta::member_size() == 2) - struct is_ellipse_compatible> : - std::bool_constant< - is_point_compatible_v, basic_point> and - is_extent_compatible_v, basic_extent> - > {}; - - // point + radius + rotation (2D) - template< - typename PointValueType, - typename RadiusValueType, - typename RotationValueType, - member_gettable_but_not_same_t> U - > - requires(meta::member_size() == 3) - struct is_ellipse_compatible> : - std::bool_constant< - is_point_compatible_v, basic_point<2, PointValueType>> and - is_extent_compatible_v, basic_extent<2, RadiusValueType>> and - std::convertible_to, RotationValueType> - > {}; - - // point + radius + rotation (3D) - template< - typename PointValueType, - typename RadiusValueType, - typename RotationValueType, - member_gettable_but_not_same_t> U - > - requires(meta::member_size() == 3) - struct is_ellipse_compatible> : - std::bool_constant< - is_point_compatible_v, basic_point<3, PointValueType>> and - is_extent_compatible_v, basic_extent<3, RadiusValueType>> and - is_extent_compatible_v, basic_extent<3, RotationValueType>> - > {}; - - template - constexpr auto is_ellipse_compatible_v = is_ellipse_compatible::value; - - template - concept ellipse_compatible_t = is_ellipse_compatible_v; - - template< - std::size_t N, - typename PointValueTypeL, - typename RadiusValueTypeL, - typename RotationValueTypeL, - typename PointValueTypeR, - typename RadiusValueTypeR, - typename RotationValueTypeR - > - [[nodiscard]] constexpr auto operator==( - const basic_ellipse& lhs, - const basic_ellipse& rhs - ) noexcept -> bool - { - return lhs.center == rhs.center and lhs.radius == rhs.radius and lhs.rotation == rhs.rotation; - } - - template< - std::size_t N, - typename PointValueType, - typename RadiusValueType, - typename RotationValueType, - ellipse_compatible_t> R - > - [[nodiscard]] constexpr auto operator==(const basic_ellipse& lhs, const R& rhs) noexcept -> bool - { - if constexpr (meta::member_size() == 2) - { - // point + radius - return lhs.center == meta::member_of_index<0>(rhs) and lhs.radius == meta::member_of_index<1>(rhs); - } - else if constexpr (meta::member_size() == 3) - { - // point + radius + rotation - return - lhs.center == meta::member_of_index<0>(rhs) and - lhs.radius == meta::member_of_index<1>(rhs) and - lhs.rotation == meta::member_of_index<2>(rhs); - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - } - - template< - std::size_t N, - typename PointValueType, - typename RadiusValueType, - typename RotationValueType, - ellipse_compatible_t> L - > - [[nodiscard]] constexpr auto operator==(const L& lhs, const basic_ellipse& rhs) noexcept -> bool - { - return rhs == lhs; - } - - // 2D - template - requires(std::is_arithmetic_v and std::is_arithmetic_v and std::is_arithmetic_v) - struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_ellipse<2, PointValueType, RadiusValueType, RotationValueType> final - : multidimensional, PointValueType, RadiusValueType, RotationValueType> - { - using point_value_type = PointValueType; - using radius_value_type = RadiusValueType; - using rotation_value_type = RotationValueType; - - using point_type = basic_point<2, point_value_type>; - using radius_type = basic_extent<2, radius_value_type>; - using rotation_type = rotation_value_type; - - constexpr static auto is_always_equal = true; - - constexpr static std::size_t element_size{3}; - template - requires(Index < element_size) - using element_type = std::conditional_t>; - - point_type center; - radius_type radius; - // multiples of PI - // e.q. .5f * std::numbers::pi_v - rotation_type rotation; - - constexpr explicit(false) basic_ellipse( - const point_value_type point_value = point_value_type{0}, - const radius_value_type radius_value = radius_value_type{0}, - const rotation_value_type rotation_value = radius_value_type{0} - ) noexcept - : center{point_value}, - radius{radius_value}, - rotation{rotation_value} {} - - constexpr basic_ellipse( - const point_value_type x, - const point_value_type y, - const radius_value_type radius_x, - const radius_value_type radius_y, - const rotation_value_type rotation_value = rotation_value_type{0} - ) noexcept - : center{x, y}, - radius{radius_x, radius_y}, - rotation{rotation_value} {} - - constexpr basic_ellipse(const point_type& point_value, const radius_type& radius_value, const rotation_type rotation_value = rotation_value_type{0}) noexcept - : center{point_value}, - radius{radius_value}, - rotation{rotation_value} {} - - template U> - constexpr explicit basic_ellipse(const U& value) noexcept - : basic_ellipse{} - { - *this = value; - } - - constexpr basic_ellipse(const basic_ellipse&) noexcept = default; - constexpr basic_ellipse(basic_ellipse&&) noexcept = default; - constexpr auto operator=(const basic_ellipse&) noexcept -> basic_ellipse& = default; - constexpr auto operator=(basic_ellipse&&) noexcept -> basic_ellipse& = default; - constexpr ~basic_ellipse() noexcept = default; - - template U> - constexpr auto operator=(const U& value) noexcept -> basic_ellipse& - { - if constexpr (meta::member_size() == 2) - { - const auto& [_center, _radius] = value; - center = static_cast(_center); - radius = static_cast(_radius); - } - else if constexpr (meta::member_size() == 3) - { - const auto& [_center, _radius, _rotation] = value; - center = static_cast(_center); - radius = static_cast(_radius); - rotation = static_cast(_rotation); - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - - return *this; - } - - template - requires(Index < 3) - [[nodiscard]] constexpr auto get() const noexcept -> std::add_lvalue_reference_t>> - { - if constexpr (Index == 0) { return center; } - else if constexpr (Index == 1) { return radius; } - else if constexpr (Index == 2) { return rotation; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template - requires(Index < 3) - [[nodiscard]] constexpr auto get() noexcept -> std::add_lvalue_reference_t> - { - if constexpr (Index == 0) { return center; } - else if constexpr (Index == 1) { return radius; } - else if constexpr (Index == 2) { return rotation; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - [[nodiscard]] constexpr auto empty() const noexcept -> bool { return radius.width == 0 or radius.height == 0; } - - [[nodiscard]] constexpr auto includes(const point_type& point) const noexcept -> bool // - { - const auto dx = static_cast(point.x - center.x); - const auto dy = static_cast(point.y - center.y); - - if (rotation == 0) // NOLINT(clang-diagnostic-float-equal) - { - return - ( - functional::pow(dx, 2) / static_cast(functional::pow(radius.width, 2)) + - functional::pow(dy, 2) / static_cast(functional::pow(radius.height, 2)) - ) - <= static_cast(1); - } - - // Rotate point back by ellipse's rotation - const auto cos_theta = functional::cos(rotation); - const auto sin_theta = functional::sin(rotation); - const auto prime_x = dx * cos_theta + dy * sin_theta; - const auto prime_y = -dx * sin_theta + dy * cos_theta; - - // Check if the point is inside the ellipse's standard form - return - ( - functional::pow(prime_x, 2) / static_cast(functional::pow(radius.width, 2)) + - functional::pow(prime_y, 2) / static_cast(functional::pow(radius.height, 2)) - ) - <= static_cast(1); - } - - [[nodiscard]] constexpr auto includes(const basic_circle<2, point_value_type, radius_value_type>& circle) const noexcept -> bool - { - const auto dx = static_cast(circle.center.x - center.x); - const auto dy = static_cast(circle.center.y - center.y); - - // Scale the circle's radius to ellipse's coordinate space - const auto scaled_radius_x = static_cast(circle.radius) / static_cast(radius.width); - const auto scaled_radius_y = static_cast(circle.radius) / static_cast(radius.height); - - if (rotation == 0) // NOLINT(clang-diagnostic-float-equal) - { - const auto distance = functional::sqrt(functional::pow(dx / static_cast(radius.width), 2) + functional::pow(dy / static_cast(radius.height), 2)); - return distance + std::ranges::max(scaled_radius_x, scaled_radius_y) <= static_cast(1); - } - - // Rotate point back by ellipse's rotation - const auto cos_theta = functional::cos(rotation); - const auto sin_theta = functional::sin(rotation); - const auto prime_x = dx * cos_theta + dy * sin_theta; - const auto prime_y = -dx * sin_theta + dy * cos_theta; - - // Check if the transformed circle is inside the unit circle - const auto distance = functional::sqrt(functional::pow(prime_x / static_cast(radius.width), 2) + functional::pow(prime_y / static_cast(radius.height), 2)); - return distance + std::ranges::max(scaled_radius_x, scaled_radius_y) <= static_cast(1); - } - - [[nodiscard]] constexpr auto includes(const basic_ellipse& ellipse) const noexcept -> bool - { - const auto dx = static_cast(ellipse.center.x - center.x); - const auto dy = static_cast(ellipse.center.y - center.y); - - // Scale the ellipse's radius to this ellipse's coordinate space - const auto scaled_rx = static_cast(ellipse.radius.width) / static_cast(radius.width); - const auto scaled_ry = static_cast(ellipse.radius.height) / static_cast(radius.height); - - if (rotation == 0) // NOLINT(clang-diagnostic-float-equal) - { - const auto distance = functional::sqrt(functional::pow(dx / static_cast(radius.width), 2) + functional::pow(dy / static_cast(radius.height), 2)); - return distance + std::ranges::max(scaled_rx, scaled_ry) <= static_cast(1); - } - - // Rotate point back by this ellipse's rotation - const auto cos_theta = functional::cos(rotation); - const auto sin_theta = functional::sin(rotation); - const auto prime_x = dx * cos_theta + dy * sin_theta; - const auto prime_y = -dx * sin_theta + dy * cos_theta; - - // Check if the transformed ellipse is inside the unit circle - const auto distance = functional::sqrt(functional::pow(prime_x / static_cast(radius.width), 2) + functional::pow(prime_y / static_cast(radius.height), 2)); - return distance + std::ranges::max(scaled_rx, scaled_ry) <= static_cast(1); - } - }; - - template - using basic_ellipse_2d = basic_ellipse<2, PointValueType, RadiusValueType, RotationValueType>; -} - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE_STD -{ - template - struct - #if defined(GAL_PROMETHEUS_COMPILER_MSVC) - [[msvc::known_semantics]] - #endif - tuple_element> // NOLINT(cert-dcl58-cpp) - { - using type = typename gal::prometheus::primitive::basic_ellipse::template element_type; - }; - - template - struct tuple_size> // NOLINT(cert-dcl58-cpp) - : std::integral_constant::element_size> {}; - - template - struct formatter> // NOLINT(cert-dcl58-cpp) - { - template - constexpr auto parse(ParseContext& context) const noexcept -> auto - { - (void)this; - return context.begin(); - } - - template - auto format(const gal::prometheus::primitive::basic_ellipse& ellipse, FormatContext& context) const noexcept -> auto - { - return std::format_to( - context.out(), - "[{}->{}({})]", - ellipse.center, - ellipse.radius, - ellipse.rotation - ); - } - }; -} diff --git a/src/primitive/extent.hpp b/src/primitive/extent.hpp new file mode 100644 index 00000000..ee0b4108 --- /dev/null +++ b/src/primitive/extent.hpp @@ -0,0 +1,197 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include + +#include + +#include + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace gal::prometheus +{ + namespace primitive + { + template + struct basic_extent; + + template + requires(std::is_arithmetic_v) + struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_extent<2, T> final : meta::dimension> + { + using value_type = T; + + value_type width; + value_type height; + + // warning: missing initializer for member ‘gal::prometheus::primitive::basic_extent<2, float>::’ [-Wmissing-field-initializers] + // {.width = width, .height = height}; + // ^ + // No initialization value specified for base class `meta::dimension` + constexpr basic_extent() noexcept + : width{}, + height{} {} + + constexpr basic_extent(const value_type width, const value_type height) noexcept + : width{width}, + height{height} {} + + constexpr explicit basic_extent(const value_type value) noexcept + : width{value}, + height{value} {} + + template + requires(Index < 2) + [[nodiscard]] constexpr auto get() const noexcept -> value_type + { + if constexpr (Index == 0) { return width; } + else if constexpr (Index == 1) { return height; } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template + requires(Index < 2) + [[nodiscard]] constexpr auto get() noexcept -> value_type& + { + if constexpr (Index == 0) { return width; } + else if constexpr (Index == 1) { return height; } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + [[nodiscard]] constexpr explicit operator basic_extent<3, value_type>() const noexcept + { + return {.width = width, .height = height, .depth = value_type{0}}; + } + }; + + template + requires std::is_arithmetic_v + struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_extent<3, T> final : meta::dimension> + { + using value_type = T; + + value_type width; + value_type height; + value_type depth; + + // warning: missing initializer for member ‘gal::prometheus::primitive::basic_extent<3, float>::’ [-Wmissing-field-initializers] + // {.width = width, .height = height, .depth = depth}; + // ^ + // No initialization value specified for base class `meta::dimension` + constexpr basic_extent() noexcept + : width{}, + height{}, + depth{} {} + + constexpr basic_extent(const value_type width, const value_type height, const value_type depth) noexcept + : width{width}, + height{height}, + depth{depth} {} + + constexpr explicit basic_extent(const value_type value) noexcept + : width{value}, + height{value}, + depth{value} {} + + template + requires(Index < 3) + [[nodiscard]] constexpr auto get() const noexcept -> value_type + { + if constexpr (Index == 0) { return width; } + else if constexpr (Index == 1) { return height; } + else if constexpr (Index == 2) { return depth; } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template + requires(Index < 3) + [[nodiscard]] constexpr auto get() noexcept -> value_type& + { + if constexpr (Index == 0) { return width; } + else if constexpr (Index == 1) { return height; } + else if constexpr (Index == 2) { return depth; } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + [[nodiscard]] constexpr explicit operator basic_extent<2, value_type>() const noexcept + { + return {.width = width, .height = height}; + } + }; + + template + using basic_extent_2d = basic_extent<2, T>; + + template + using basic_extent_3d = basic_extent<3, T>; + } + + namespace meta + { + // This makes `extent1 == extent2` return boolean instead of array + template + struct dimension_folder, DimensionFoldOperation::EQUAL> + { + constexpr static auto value = DimensionFoldCategory::ALL; + }; + + // This makes `extent1 != extent2` return boolean instead of array + template + struct dimension_folder, DimensionFoldOperation::NOT_EQUAL> + { + constexpr static auto value = DimensionFoldCategory::ANY; + }; + } +} + +namespace std +{ + template + struct + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + [[msvc::known_semantics]] + #endif + tuple_element> // NOLINT(cert-dcl58-cpp) + { + using type = T; + }; + + template + struct tuple_size> // NOLINT(cert-dcl58-cpp) + : std::integral_constant {}; + + template + struct formatter> // NOLINT(cert-dcl58-cpp) + { + template + constexpr auto parse(ParseContext& context) const noexcept -> auto + { + (void)this; + return context.begin(); + } + + template + auto format(const gal::prometheus::primitive::basic_extent& extent, FormatContext& context) const noexcept -> auto + { + if constexpr (N == 2) + { + return std::format_to(context.out(), "[{},{}]", extent.width, extent.height); + } + else if constexpr (N == 3) + { + return std::format_to(context.out(), "[{},{},{}]", extent.width, extent.height, extent.depth); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + }; +} // namespace std diff --git a/src/primitive/extent.ixx b/src/primitive/extent.ixx deleted file mode 100644 index e0562fc5..00000000 --- a/src/primitive/extent.ixx +++ /dev/null @@ -1,311 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.primitive:extent; - -import std; -import gal.prometheus.meta; - -import :multidimensional; -import :point; - -#else -#pragma once - -#include -#include -#include -#include - -#include -#include -#include -#include - -#endif - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::primitive) -{ - template - requires std::is_arithmetic_v - struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_extent; - - template - struct is_basic_extent : std::false_type {}; - - template - struct is_basic_extent> : std::true_type {}; - - template - constexpr auto is_basic_extent_v = is_basic_extent::value; - - template - concept basic_extent_t = is_basic_extent_v; - - template - struct is_extent_compatible : std::false_type {}; - - template> OtherType> - requires(meta::member_size() == 2) - struct is_extent_compatible> : - std::bool_constant< - std::convertible_to, ExtentValueType> and - std::convertible_to, ExtentValueType> - > {}; - - template> OtherType> - requires(meta::member_size() == 3) - struct is_extent_compatible> : - std::bool_constant< - std::convertible_to, ExtentValueType> and - std::convertible_to, ExtentValueType> and - std::convertible_to, ExtentValueType> - > {}; - - template - constexpr auto is_extent_compatible_v = is_extent_compatible::value; - - template - concept extent_compatible_t = is_extent_compatible_v; - - template - [[nodiscard]] constexpr auto operator==(const basic_extent& lhs, const basic_extent& rhs) noexcept -> bool - { - if constexpr (N == 2) - { - return lhs.width == rhs.width and lhs.height == rhs.height; - } - else if constexpr (N == 3) - { - return lhs.width == rhs.width and lhs.height == rhs.height and lhs.depth == rhs.depth; - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - } - - template> R> - [[nodiscard]] constexpr auto operator==(const basic_extent& lhs, const R& rhs) noexcept -> bool - { - if constexpr (N == 2) - { - return lhs.width == meta::member_of_index<0>(rhs) and lhs.height == meta::member_of_index<1>(rhs); - } - else if constexpr (N == 3) - { - return - lhs.width == meta::member_of_index<0>(rhs) and - lhs.height == meta::member_of_index<1>(rhs) and - lhs.depth == meta::member_of_index<2>(rhs); - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - } - - template> L> - [[nodiscard]] constexpr auto operator==(const L& lhs, const basic_extent& rhs) noexcept -> bool - { - return rhs == lhs; - } - - template - requires std::is_arithmetic_v - struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_extent<2, T> final : multidimensional, T> - { - using value_type = T; - - constexpr static auto is_always_equal = true; - - value_type width; - value_type height; - - constexpr explicit(false) basic_extent(const value_type value = value_type{0}) noexcept - : width{value}, - height{value} {} - - constexpr basic_extent(const value_type width, const value_type height) noexcept - : width{width}, - height{height} {} - - template U> - constexpr explicit basic_extent(const U& value) noexcept - : basic_extent{} - { - *this = value; - } - - constexpr basic_extent(const basic_extent&) noexcept = default; - constexpr basic_extent(basic_extent&&) noexcept = default; - constexpr auto operator=(const basic_extent&) noexcept -> basic_extent& = default; - constexpr auto operator=(basic_extent&&) noexcept -> basic_extent& = default; - constexpr ~basic_extent() noexcept = default; - - template U> - constexpr auto operator=(const U& value) noexcept -> basic_extent& - { - const auto [_width, _height] = value; - width = static_cast(_width); - height = static_cast(_height); - - return *this; - } - - template - requires(Index < 2) - [[nodiscard]] constexpr auto get() const noexcept -> const value_type& - { - if constexpr (Index == 0) { return width; } - else if constexpr (Index == 1) { return height; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template - requires(Index < 2) - [[nodiscard]] constexpr auto get() noexcept -> value_type& - { - if constexpr (Index == 0) { return width; } - else if constexpr (Index == 1) { return height; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - [[nodiscard]] constexpr explicit operator basic_extent<3, value_type>() const noexcept { return {width, height, 0}; } - }; - - template - requires std::is_arithmetic_v - struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_extent<3, T> final : multidimensional, T> - { - using value_type = T; - - constexpr static auto is_always_equal = true; - - value_type width; - value_type height; - value_type depth; - - constexpr explicit(false) basic_extent(const value_type value = value_type{0}) noexcept - : width{value}, - height{value}, - depth{value} {} - - constexpr basic_extent(const value_type width, const value_type height, const value_type depth) noexcept - : width{width}, - height{height}, - depth{depth} {} - - template U> - constexpr explicit basic_extent(const U& value) noexcept - : basic_extent{} - { - *this = value; - } - - constexpr basic_extent(const basic_extent&) noexcept = default; - constexpr basic_extent(basic_extent&&) noexcept = default; - constexpr auto operator=(const basic_extent&) noexcept -> basic_extent& = default; - constexpr auto operator=(basic_extent&&) noexcept -> basic_extent& = default; - constexpr ~basic_extent() noexcept = default; - - template U> - constexpr auto operator=(const U& value) noexcept -> basic_extent& - { - const auto [_width, _height, _depth] = value; - width = static_cast(_width); - height = static_cast(_height); - depth = static_cast(_depth); - - return *this; - } - - template - requires(Index < 3) - [[nodiscard]] constexpr auto get() const noexcept -> value_type - { - if constexpr (Index == 0) { return width; } - else if constexpr (Index == 1) { return height; } - else if constexpr (Index == 2) { return depth; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template - requires(Index < 3) - [[nodiscard]] constexpr auto get() noexcept -> value_type& - { - if constexpr (Index == 0) { return width; } - else if constexpr (Index == 1) { return height; } - else if constexpr (Index == 2) { return depth; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - [[nodiscard]] constexpr explicit(false) operator basic_extent<2, value_type>() const noexcept { return {width, height}; } - }; - - template - using basic_extent_2d = basic_extent<2, T>; - - template - using basic_extent_3d = basic_extent<3, T>; -} - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE_STD -{ - template - struct - #if defined(GAL_PROMETHEUS_COMPILER_MSVC) - [[msvc::known_semantics]] - #endif - tuple_element> // NOLINT(cert-dcl58-cpp) - { - using type = T; - }; - - template - struct tuple_size> // NOLINT(cert-dcl58-cpp) - : std::integral_constant {}; - - template - struct formatter> // NOLINT(cert-dcl58-cpp) - { - template - constexpr auto parse(ParseContext& context) const noexcept -> auto - { - (void)this; - return context.begin(); - } - - template - auto format(const gal::prometheus::primitive::basic_extent& extent, FormatContext& context) const noexcept -> auto - { - if constexpr (N == 2) - { - return std::format_to( - context.out(), - "({},{})", - extent.width, - extent.height - ); - } - else if constexpr (N == 3) - { - return std::format_to( - context.out(), - "({},{},{})", - extent.width, - extent.height, - extent.depth - ); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - }; -} diff --git a/src/primitive/multidimensional.ixx b/src/primitive/multidimensional.ixx deleted file mode 100644 index ff45a67d..00000000 --- a/src/primitive/multidimensional.ixx +++ /dev/null @@ -1,705 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.primitive:multidimensional; - -import std; -import gal.prometheus.meta; - -#else -#pragma once - -#include - -#include -#include - -#endif - -namespace gal::prometheus::primitive -{ - namespace multidimensional_detail - { - template - struct lazy_value_type; - - template - struct lazy_value_type - { - using type = std::tuple_element_t; - }; - - template - struct lazy_value_type - { - using type = typename Derived::value_type; - }; - - template - using lazy_value_type_t = typename lazy_value_type< - Index, - Derived, - requires - { - // ReSharper disable once CppUseTypeTraitAlias - typename std::tuple_element::type; - }>::type; - - template - struct is_always_equal : std::false_type {}; - - template - requires requires { T::is_always_equal; } - struct is_always_equal : std::bool_constant {}; - - template - constexpr static auto is_always_equal_v = is_always_equal::value; - - template - concept always_equal_t = is_always_equal_v; - } - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - - enum class Dimension - { - _0 = 0, - _1 = 1, - _2 = 2, - _3 = 3, - }; - - template - requires (std::is_arithmetic_v and ...) - struct [[nodiscard]] multidimensional - { - template - requires (std::is_arithmetic_v and ...) - friend struct multidimensional; - - using value_type = std::tuple; - - using derived_type = Derived; - - private: - [[nodiscard]] constexpr auto rep() noexcept -> Derived& { return *static_cast(this); } - [[nodiscard]] constexpr auto rep() const noexcept -> const Derived& { return *static_cast(this); } - - public: - template - requires( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - [[nodiscard]] constexpr auto to() const noexcept -> OtherDerived - { - if constexpr (std::is_same_v) { return *this; } - else - { - OtherDerived result; - - meta::member_zip_walk( - [](auto& lhs, const auto rhs) noexcept -> void // - { - using type = multidimensional_detail::lazy_value_type_t; - lhs = static_cast(rhs); - }, - result, - rep() - ); - - return result; - } - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - constexpr auto operator+=(const multidimensional& other) noexcept -> derived_type& - { - meta::member_zip_walk( - [](auto& lhs, const auto rhs) noexcept -> void // - { - using type = multidimensional_detail::lazy_value_type_t; - lhs += static_cast(rhs); - }, - rep(), - other.rep() - ); - return rep(); - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - [[nodiscard]] constexpr auto operator+(const multidimensional& other) const noexcept -> derived_type - { - derived_type result{rep()}; - - result += other; - - return result; - } - - template> U> - constexpr auto operator+=(const U value) noexcept -> derived_type& - { - meta::member_zip_walk( - [value](auto& self) noexcept -> void // - { - using type = multidimensional_detail::lazy_value_type_t; - self += static_cast(static_cast>(value)); - }, - rep() - ); - return rep(); - } - - template> U> - [[nodiscard]] constexpr auto operator+(const U value) const noexcept -> derived_type - { - derived_type result{rep()}; - - result += value; - - return result; - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - constexpr auto operator-=(const multidimensional& other) noexcept -> derived_type& - { - meta::member_zip_walk( - [](auto& lhs, const auto rhs) noexcept -> void // - { - using type = multidimensional_detail::lazy_value_type_t; - lhs -= static_cast(rhs); - }, - rep(), - other.rep() - ); - return rep(); - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - [[nodiscard]] constexpr auto operator-(const multidimensional& other) const noexcept -> derived_type - { - derived_type result{rep()}; - - result -= other; - - return result; - } - - template> U> - constexpr auto operator-=(const U value) noexcept -> derived_type& - { - meta::member_zip_walk( - [value](auto& self) noexcept -> void // - { - using type = multidimensional_detail::lazy_value_type_t; - self -= static_cast(static_cast>(value)); - }, - rep() - ); - return rep(); - } - - template> U> - [[nodiscard]] constexpr auto operator-(const U value) const noexcept -> derived_type - { - derived_type result{rep()}; - - result -= value; - - return result; - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - constexpr auto operator*=(const multidimensional& other) noexcept -> derived_type& - { - meta::member_zip_walk( - [](auto& lhs, const auto rhs) noexcept -> void // - { - using type = multidimensional_detail::lazy_value_type_t; - lhs *= static_cast(rhs); - }, - rep(), - other.rep() - ); - return rep(); - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - [[nodiscard]] constexpr auto operator*(const multidimensional& other) const noexcept -> derived_type - { - derived_type result{rep()}; - - result *= other; - - return result; - } - - template> U> - constexpr auto operator*=(const U value) noexcept -> derived_type& - { - meta::member_zip_walk( - [value](auto& self) noexcept -> void // - { - using type = multidimensional_detail::lazy_value_type_t; - self *= static_cast(static_cast>(value)); - }, - rep() - ); - return rep(); - } - - template> U> - [[nodiscard]] constexpr auto operator*(const U value) const noexcept -> derived_type - { - derived_type result{rep()}; - - result *= value; - - return result; - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - constexpr auto operator/=(const multidimensional& other) noexcept -> derived_type& - { - meta::member_zip_walk( - [](auto& lhs, const auto rhs) noexcept -> void // - { - using type = multidimensional_detail::lazy_value_type_t; - lhs /= static_cast(rhs); - }, - rep(), - other.rep() - ); - return rep(); - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - [[nodiscard]] constexpr auto operator/(const multidimensional& other) const noexcept -> derived_type - { - derived_type result{rep()}; - - result /= other; - - return result; - } - - template> U> - constexpr auto operator/=(const U value) noexcept -> derived_type& - { - meta::member_zip_walk( - [value](auto& self) noexcept -> void // - { - using type = multidimensional_detail::lazy_value_type_t; - self /= static_cast(static_cast>(value)); - }, - rep() - ); - return rep(); - } - - template> U> - [[nodiscard]] constexpr auto operator/(const U value) const noexcept -> derived_type - { - derived_type result{rep()}; - - result /= value; - - return result; - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - constexpr auto operator%=(const multidimensional& other) noexcept -> derived_type& - { - meta::member_zip_walk( - [](auto& lhs, const auto rhs) noexcept -> void // - { - using type = multidimensional_detail::lazy_value_type_t; - lhs %= static_cast(rhs); - }, - rep(), - other.rep() - ); - return rep(); - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - [[nodiscard]] constexpr auto operator%(const multidimensional& other) const noexcept -> derived_type - { - derived_type result{rep()}; - - result %= other; - - return result; - } - - template> U> - constexpr auto operator%=(const U value) noexcept -> derived_type& - { - meta::member_zip_walk( - [value](auto& self) noexcept -> void // - { - using type = multidimensional_detail::lazy_value_type_t; - self %= static_cast(static_cast>(value)); - }, - rep() - ); - return rep(); - } - - template> U> - [[nodiscard]] constexpr auto operator%(const U value) const noexcept -> derived_type - { - derived_type result{rep()}; - - result %= value; - - return result; - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - constexpr auto operator&=(const multidimensional& other) noexcept -> derived_type& - { - meta::member_zip_walk( - [](auto& lhs, const auto rhs) noexcept -> void // - { - using type = multidimensional_detail::lazy_value_type_t; - lhs &= static_cast(rhs); - }, - rep(), - other.rep() - ); - return rep(); - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - [[nodiscard]] constexpr auto operator&(const multidimensional& other) const noexcept -> derived_type - { - derived_type result{rep()}; - - result &= other; - - return result; - } - - template> U> - constexpr auto operator&=(const U value) noexcept -> derived_type& - { - meta::member_zip_walk( - [value](auto& self) noexcept -> void // - { - using type = multidimensional_detail::lazy_value_type_t; - self &= static_cast(static_cast>(value)); - }, - rep() - ); - return rep(); - } - - template> U> - [[nodiscard]] constexpr auto operator&(const U value) const noexcept -> derived_type - { - derived_type result{rep()}; - - result &= value; - - return result; - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - constexpr auto operator|=(const multidimensional& other) noexcept -> derived_type& - { - meta::member_zip_walk( - [](auto& lhs, const auto rhs) noexcept -> void // - { - using type = multidimensional_detail::lazy_value_type_t; - lhs |= static_cast(rhs); - }, - rep(), - other.rep() - ); - return rep(); - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - [[nodiscard]] constexpr auto operator|(const multidimensional& other) const noexcept -> derived_type - { - derived_type result{rep()}; - - result |= other; - - return result; - } - - template> U> - constexpr auto operator|=(const U value) noexcept -> derived_type& - { - meta::member_zip_walk( - [value](auto& self) noexcept -> void // - { - using type = multidimensional_detail::lazy_value_type_t; - self |= static_cast(static_cast>(value)); - }, - rep() - ); - return rep(); - } - - template> U> - [[nodiscard]] constexpr auto operator|(const U value) const noexcept -> derived_type - { - derived_type result{rep()}; - - result |= value; - - return result; - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) and - (std::three_way_comparable_with and ...) - [[nodiscard]] constexpr auto compare(const multidimensional& other) noexcept -> auto - { - const auto v = meta::member_of_index(D)>(rep()); - const auto other_v = meta::member_of_index(D)>(other.rep()); - - return v <=> other_v; - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) and - (std::declval(std::declval(), std::declval()) and ...) - [[nodiscard]] constexpr auto compare(Comparator comparator, const multidimensional& other) noexcept -> bool - { - return [&](std::index_sequence) noexcept -> bool - { - const auto f = [&]() noexcept -> bool - { - return comparator(meta::member_of_index(rep()), meta::member_of_index(other.rep())); - }; - - return (f.template operator()() and ...); - }(); - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - [[nodiscard]] constexpr auto equal(const multidimensional& other) noexcept -> bool - { - return this->compare(std::ranges::equal_to{}, other); - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - [[nodiscard]] constexpr auto not_equal(const multidimensional& other) noexcept -> bool - { - return this->compare(std::ranges::not_equal_to{}, other); - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - [[nodiscard]] constexpr auto greater_than(const multidimensional& other) noexcept -> bool - { - return this->compare(std::ranges::greater{}, other); - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - [[nodiscard]] constexpr auto greater_equal(const multidimensional& other) noexcept -> bool - { - return this->compare(std::ranges::greater_equal{}, other); - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - [[nodiscard]] constexpr auto less_than(const multidimensional& other) noexcept -> bool - { - return this->compare(std::ranges::less{}, other); - } - - template... Us> - requires( - std::is_same_v or - ( - multidimensional_detail::always_equal_t and - multidimensional_detail::always_equal_t and - meta::member_size() == meta::member_size() - ) - ) - [[nodiscard]] constexpr auto less_equal(const multidimensional& other) noexcept -> bool - { - return this->compare(std::ranges::less_equal{}, other); - } - }; - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -} diff --git a/src/primitive/point.hpp b/src/primitive/point.hpp new file mode 100644 index 00000000..b54daa76 --- /dev/null +++ b/src/primitive/point.hpp @@ -0,0 +1,358 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include + +#include + +#include +#include + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace gal::prometheus +{ + namespace primitive + { + template + struct basic_point; + + template + requires(std::is_arithmetic_v) + struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_point<2, T> final : meta::dimension> + { + using value_type = T; + + value_type x; + value_type y; + + // warning: missing initializer for member ‘gal::prometheus::primitive::basic_point<2, float>::’ [-Wmissing-field-initializers] + // {.x = x, .y = y}; + // ^ + // No initialization value specified for base class `meta::dimension` + constexpr basic_point() noexcept + : x{}, + y{} {} + + constexpr basic_point(const value_type x, const value_type y) noexcept + : x{x}, + y{y} {} + + constexpr explicit basic_point(const value_type value) noexcept + : x{value}, + y{value} {} + + template + requires(Index < 2) + [[nodiscard]] constexpr auto get() const noexcept -> value_type + { + if constexpr (Index == 0) { return x; } + else if constexpr (Index == 1) { return y; } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template + requires(Index < 2) + [[nodiscard]] constexpr auto get() noexcept -> value_type& + { + if constexpr (Index == 0) { return x; } + else if constexpr (Index == 1) { return y; } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + [[nodiscard]] constexpr explicit operator basic_point<3, value_type>() const noexcept + { + return {.x = x, .y = y, .z = value_type{0}}; + } + + template U = value_type> + [[nodiscard]] constexpr auto distance(const basic_point<2, U>& other) const noexcept -> value_type // + { + return math::hypot(x - static_cast(other.x), y - static_cast(other.y)); + } + + template Low = value_type, std::convertible_to High = value_type> + [[nodiscard]] constexpr auto clamp( + const basic_point<2, Low>& low, + const basic_point<2, High>& high + ) noexcept -> basic_point& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.x < high.x); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.y < high.y); + + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED + { + x = std::ranges::min(std::ranges::max(x, static_cast(low.x)), static_cast(high.x)); + y = std::ranges::min(std::ranges::max(y, static_cast(low.y)), static_cast(high.y)); + } + else + { + x = std::ranges::clamp(x, low.x, high.x); + y = std::ranges::clamp(y, low.y, high.y); + } + + return *this; + } + + template Low = value_type, std::convertible_to High = value_type> + [[nodiscard]] friend constexpr auto clamp( + const basic_point& point, + const basic_point<2, Low>& low, + const basic_point<2, High>& high + ) noexcept -> basic_point + { + auto result{point}; + + result.clamp(low, high); + return result; + } + + template T1 = value_type, std::convertible_to T2 = value_type> + requires (Index < 2) + [[nodiscard]] constexpr auto between( + const basic_point<2, T1>& p1, + const basic_point<2, T2>& p2 + ) const noexcept -> bool + { + if constexpr (Index == 0) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(p1.x) < static_cast(p2.x)); + + return x >= static_cast(p1.x) and x < static_cast(p2.x); + } + else if constexpr (Index == 1) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(p1.y) < static_cast(p2.y)); + + return y >= static_cast(p1.y) and y < static_cast(p2.y); + } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template T1 = value_type, std::convertible_to T2 = value_type> + [[nodiscard]] constexpr auto between( + const basic_point<2, T1>& p1, + const basic_point<2, T2>& p2 + ) const noexcept -> bool + { + return + this->template between<0>(p1, p2) and + this->template between<1>(p1, p2); + } + }; + + template + requires(std::is_arithmetic_v) + struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_point<3, T> final : meta::dimension> + { + using value_type = T; + + value_type x; + value_type y; + value_type z; + + // warning: missing initializer for member ‘gal::prometheus::primitive::basic_point<3, float>::’ [-Wmissing-field-initializers] + // {.x = x, .y = y, .z = z}; + // ^ + // No initialization value specified for base class `meta::dimension` + constexpr basic_point() noexcept + : x{}, + y{}, + z{} {} + + constexpr basic_point(const value_type x, const value_type y, const value_type z) noexcept + : x{x}, + y{y}, + z{z} {} + + constexpr explicit basic_point(const value_type value) noexcept + : x{value}, + y{value}, + z{value} {} + + template + requires(Index < 3) + [[nodiscard]] constexpr auto get() const noexcept -> value_type + { + if constexpr (Index == 0) { return x; } + else if constexpr (Index == 1) { return y; } + else if constexpr (Index == 2) { return z; } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template + requires(Index < 3) + [[nodiscard]] constexpr auto get() noexcept -> value_type& + { + if constexpr (Index == 0) { return x; } + else if constexpr (Index == 1) { return y; } + else if constexpr (Index == 2) { return z; } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + [[nodiscard]] constexpr explicit operator basic_point<2, value_type>() const noexcept + { + return {.x = x, .y = y}; + } + + template U = value_type> + [[nodiscard]] constexpr auto distance(const basic_point<3, U>& other) const noexcept -> value_type + { + return math::hypot(x - static_cast(other.x), y - static_cast(other.y), z - static_cast(other.z)); + } + + template Low = value_type, std::convertible_to High = value_type> + [[nodiscard]] constexpr auto clamp( + const basic_point<3, Low>& low, + const basic_point<3, High>& high + ) noexcept -> basic_point& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.x < high.x); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.y < high.y); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.z < high.z); + + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED + { + x = std::ranges::min(std::ranges::max(x, static_cast(low.x)), static_cast(high.x)); + y = std::ranges::min(std::ranges::max(y, static_cast(low.y)), static_cast(high.y)); + z = std::ranges::min(std::ranges::max(z, static_cast(low.z)), static_cast(high.z)); + } + else + { + x = std::ranges::clamp(x, low.x, high.x); + y = std::ranges::clamp(y, low.y, high.y); + z = std::ranges::clamp(z, low.z, high.z); + } + + return *this; + } + + template Low = value_type, std::convertible_to High = value_type> + [[nodiscard]] friend constexpr auto clamp( + const basic_point& point, + const basic_point<3, Low>& low, + const basic_point<3, High>& high + ) noexcept -> basic_point + { + auto result{point}; + + result.clamp(low, high); + return result; + } + + template T1 = value_type, std::convertible_to T2 = value_type> + requires (Index < 3) + [[nodiscard]] constexpr auto between( + const basic_point<3, T1>& p1, + const basic_point<3, T2>& p2 + ) const noexcept -> bool + { + if constexpr (Index == 0) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(p1.x) < static_cast(p2.x)); + + return x >= static_cast(p1.x) and x < static_cast(p2.x); + } + else if constexpr (Index == 1) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(p1.y) < static_cast(p2.y)); + + return y >= static_cast(p1.y) and y < static_cast(p2.y); + } + else if constexpr (Index == 2) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(p1.z) < static_cast(p2.z)); + + return z >= static_cast(p1.z) and z < static_cast(p2.z); + } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template T1 = value_type, std::convertible_to T2 = value_type> + [[nodiscard]] constexpr auto between( + const basic_point<3, T1>& p1, + const basic_point<3, T2>& p2 + ) const noexcept -> bool + { + return + this->template between<0>(p1, p2) and + this->template between<1>(p1, p2) and + this->template between<2>(p1, p2); + } + }; + + template + using basic_point_2d = basic_point<2, T>; + + template + using basic_point_3d = basic_point<3, T>; + } + + namespace meta + { + // This makes `point1 == point2` return a boolean instead of array + template + struct dimension_folder, DimensionFoldOperation::EQUAL> + { + constexpr static auto value = DimensionFoldCategory::ALL; + }; + + // This makes `point1 != point2` return a boolean instead of array + template + struct dimension_folder, DimensionFoldOperation::NOT_EQUAL> + { + constexpr static auto value = DimensionFoldCategory::ANY; + }; + } +} + +namespace std +{ + template + struct + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + [[msvc::known_semantics]] + #endif + tuple_element> // NOLINT(cert-dcl58-cpp) + { + using type = T; + }; + + template + struct tuple_size> // NOLINT(cert-dcl58-cpp) + : std::integral_constant {}; + + template + struct formatter> // NOLINT(cert-dcl58-cpp) + { + template + constexpr auto parse(ParseContext& context) const noexcept -> auto + { + (void)this; + return context.begin(); + } + + template + auto format(const gal::prometheus::primitive::basic_point& point, FormatContext& context) const noexcept -> auto + { + if constexpr (N == 2) + { + return std::format_to(context.out(), "({},{})", point.x, point.y); + } + else if constexpr (N == 3) + { + return std::format_to(context.out(), "({},{},{})", point.x, point.y, point.z); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + }; +} // namespace std diff --git a/src/primitive/point.ixx b/src/primitive/point.ixx deleted file mode 100644 index f63c3d24..00000000 --- a/src/primitive/point.ixx +++ /dev/null @@ -1,450 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.primitive:point; - -import std; -import gal.prometheus.functional; -import gal.prometheus.meta; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -import :multidimensional; - -#else -#pragma once - -#include -#include - -#include -#include -#include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -#endif - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::primitive) -{ - enum class DirectionCategory - { - X, - Y, - Z, - - ALL, - }; - - template - requires std::is_arithmetic_v - struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_point; - - template - struct is_basic_point : std::false_type {}; - - template - struct is_basic_point> : std::true_type {}; - - template - constexpr auto is_basic_point_v = is_basic_point::value; - - template - concept basic_point_t = is_basic_point_v; - - template - struct is_point_compatible : std::false_type {}; - - // force use copy-ctor - template - concept member_gettable_but_not_same_t = (not std::is_same_v) and meta::member_gettable_t; - - template - struct is_point_compatible : std::false_type {}; - - template> U> - requires(meta::member_size() == 2) - struct is_point_compatible> : - std::bool_constant< - std::convertible_to, PointValueType> and - std::convertible_to, PointValueType> - > {}; - - template> U> - requires(meta::member_size() == 3) - struct is_point_compatible> : - std::bool_constant< - std::convertible_to, PointValueType> and - std::convertible_to, PointValueType> and - std::convertible_to, PointValueType> - > {}; - - template - constexpr auto is_point_compatible_v = is_point_compatible::value; - - template - concept point_compatible_t = is_point_compatible_v; - - template - [[nodiscard]] constexpr auto operator==(const basic_point& lhs, const basic_point& rhs) noexcept -> bool - { - if constexpr (N == 2) - { - return lhs.x == rhs.x and lhs.y == rhs.y; - } - else if constexpr (N == 3) - { - return lhs.x == rhs.x and lhs.y == rhs.y and lhs.z == rhs.z; - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - } - - template> R> - [[nodiscard]] constexpr auto operator==(const basic_point& lhs, const R& rhs) noexcept -> bool - { - if constexpr (N == 2) - { - return lhs.x == meta::member_of_index<0>(rhs) and lhs.y == meta::member_of_index<1>(rhs); - } - else if constexpr (N == 3) - { - return - lhs.x == meta::member_of_index<0>(rhs) and - lhs.y == meta::member_of_index<1>(rhs) and - lhs.z == meta::member_of_index<2>(rhs); - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - } - - template> L> - [[nodiscard]] constexpr auto operator==(const L& lhs, const basic_point& rhs) noexcept -> bool - { - return rhs == lhs; - } - - template - requires std::is_arithmetic_v - struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_point<2, T> final : multidimensional, T> - { - using value_type = T; - - constexpr static auto is_always_equal = true; - - value_type x; - value_type y; - - constexpr explicit(false) basic_point(const value_type value = value_type{0}) noexcept - : x{value}, - y{value} {} - - constexpr basic_point(const value_type x, const value_type y) noexcept - : x{x}, - y{y} {} - - template U> - constexpr explicit basic_point(const U& value) noexcept - : basic_point{} - { - *this = value; - } - - constexpr basic_point(const basic_point&) noexcept = default; - constexpr basic_point(basic_point&&) noexcept = default; - constexpr auto operator=(const basic_point&) noexcept -> basic_point& = default; - constexpr auto operator=(basic_point&&) noexcept -> basic_point& = default; - constexpr ~basic_point() noexcept = default; - - template U> - constexpr auto operator=(const U& value) noexcept -> basic_point& - { - const auto [_x, _y] = value; - x = static_cast(_x); - y = static_cast(_y); - - return *this; - } - - template - requires(Index < 2) - [[nodiscard]] constexpr auto get() const noexcept -> const value_type& - { - if constexpr (Index == 0) { return x; } - else if constexpr (Index == 1) { return y; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template - requires(Index < 2) - [[nodiscard]] constexpr auto get() noexcept -> value_type& - { - if constexpr (Index == 0) { return x; } - else if constexpr (Index == 1) { return y; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - [[nodiscard]] constexpr explicit operator basic_point<3, value_type>() const noexcept { return {x, y, 0}; } - - template U = value_type> - [[nodiscard]] constexpr auto distance(const basic_point<2, U>& other) const noexcept -> value_type // - { - return functional::hypot(x - static_cast(other.x), y - static_cast(other.y)); - } - - template Low = value_type, std::convertible_to High = value_type> - [[nodiscard]] constexpr auto clamp(const basic_point<2, Low>& low, const basic_point<2, High>& high) noexcept -> basic_point& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.x < high.x); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.y < high.y); - - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED - { - x = std::ranges::min(std::ranges::max(x, static_cast(low.x)), static_cast(high.x)); - y = std::ranges::min(std::ranges::max(y, static_cast(low.y)), static_cast(high.y)); - } - else - { - x = std::ranges::clamp(x, low.x, high.x); - y = std::ranges::clamp(y, low.y, high.y); - } - - return *this; - } - - template Low = value_type, std::convertible_to High = value_type> - [[nodiscard]] friend constexpr auto clamp( - const basic_point& point, - const basic_point<2, Low>& low, - const basic_point<2, High>& high - ) noexcept -> basic_point - { - auto result{point}; - - result.clamp(low, high); - return result; - } - - template T1 = value_type, std::convertible_to T2 = value_type> - [[nodiscard]] constexpr auto between(const basic_point<2, T1>& left_top, const basic_point<2, T2>& right_bottom) const noexcept -> bool - { - if constexpr (Category == DirectionCategory::ALL) // - { - return - between(left_top, right_bottom) and - between(left_top, right_bottom); - } - else if constexpr (Category == DirectionCategory::X) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(left_top.x) < static_cast(right_bottom.x)); - - return x >= static_cast(left_top.x) and x < static_cast(right_bottom.x); - } - else if constexpr (Category == DirectionCategory::Y) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(left_top.y) < static_cast(right_bottom.y)); - - return y >= static_cast(left_top.y) and y < static_cast(right_bottom.y); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - }; - - template - requires std::is_arithmetic_v - struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_point<3, T> final : multidimensional, T> - { - using value_type = T; - - constexpr static auto is_always_equal = true; - - value_type x; - value_type y; - value_type z; - - constexpr explicit(false) basic_point(const value_type value = value_type{0}) noexcept - : x{value}, - y{value}, - z{value} {} - - constexpr basic_point(const value_type x, const value_type y, const value_type z) noexcept - : x{x}, - y{y}, - z{z} {} - - template U> - constexpr explicit basic_point(const U& value) noexcept - : basic_point{} - { - *this = value; - } - - constexpr basic_point(const basic_point&) noexcept = default; - constexpr basic_point(basic_point&&) noexcept = default; - constexpr auto operator=(const basic_point&) noexcept -> basic_point& = default; - constexpr auto operator=(basic_point&&) noexcept -> basic_point& = default; - constexpr ~basic_point() noexcept = default; - - template U> - constexpr auto operator=(const U& value) noexcept -> basic_point& - { - const auto [_x, _y, _z] = value; - x = static_cast(_x); - y = static_cast(_y); - z = static_cast(_z); - - return *this; - } - - template - requires(Index < 3) - [[nodiscard]] constexpr auto get() const noexcept -> value_type - { - if constexpr (Index == 0) { return x; } - else if constexpr (Index == 1) { return y; } - else if constexpr (Index == 2) { return z; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template - requires(Index < 3) - [[nodiscard]] constexpr auto get() noexcept -> value_type& - { - if constexpr (Index == 0) { return x; } - else if constexpr (Index == 1) { return y; } - else if constexpr (Index == 2) { return z; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - [[nodiscard]] constexpr explicit(false) operator basic_point<2, value_type>() const noexcept { return {x, y}; } - - template U = value_type> - [[nodiscard]] constexpr auto distance(const basic_point<2, U>& other) const noexcept -> value_type // - { - return functional::hypot( - x - static_cast(other.x), - y - static_cast(other.y), - z - static_cast(other.z) - ); - } - - template Low = value_type, std::convertible_to High = value_type> - [[nodiscard]] constexpr auto clamp(const basic_point<3, Low>& low, const basic_point<3, High>& high) noexcept -> basic_point& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.x < high.x); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.y < high.y); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(low.z < high.z); - - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED - { - x = std::ranges::min(std::ranges::max(x, static_cast(low.x)), static_cast(high.x)); - y = std::ranges::min(std::ranges::max(y, static_cast(low.y)), static_cast(high.y)); - z = std::ranges::min(std::ranges::max(z, static_cast(low.z)), static_cast(high.z)); - } - else - { - x = std::ranges::clamp(x, low.x, high.x); - y = std::ranges::clamp(y, low.y, high.y); - z = std::ranges::clamp(z, low.z, high.z); - } - - return *this; - } - - template Low = value_type, std::convertible_to High = value_type> - [[nodiscard]] friend constexpr auto clamp( - const basic_point& point, - const basic_point<3, Low>& low, - const basic_point<3, High>& high - ) noexcept -> basic_point - { - auto result{point}; - - result.clamp(low, high); - return result; - } - - template T1 = value_type, std::convertible_to T2 = value_type> - [[nodiscard]] constexpr auto between(const basic_point<3, T1>& left_top, const basic_point<3, T2>& right_bottom) const noexcept -> bool - { - if constexpr (Category == DirectionCategory::ALL) // - { - return - between(left_top, right_bottom) and - between(left_top, right_bottom) and - between(left_top, right_bottom); - } - else if constexpr (Category == DirectionCategory::X) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(left_top.x) < static_cast(right_bottom.x)); - - return x >= static_cast(left_top.x) and x < static_cast(right_bottom.x); - } - else if constexpr (Category == DirectionCategory::Y) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(left_top.y) < static_cast(right_bottom.y)); - - return y >= static_cast(left_top.y) and y < static_cast(right_bottom.y); - } - else if constexpr (Category == DirectionCategory::Z) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(static_cast(left_top.z) < static_cast(right_bottom.z)); - - return z >= static_cast(left_top.z) and z < static_cast(right_bottom.z); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - }; - - template - using basic_point_2d = basic_point<2, T>; - - template - using basic_point_3d = basic_point<3, T>; -} - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE_STD -{ - template - struct - #if defined(GAL_PROMETHEUS_COMPILER_MSVC) - [[msvc::known_semantics]] - #endif - tuple_element> // NOLINT(cert-dcl58-cpp) - { - using type = T; - }; - - template - struct tuple_size> // NOLINT(cert-dcl58-cpp) - : std::integral_constant {}; - - template - struct formatter> // NOLINT(cert-dcl58-cpp) - { - template - constexpr auto parse(ParseContext& context) const noexcept -> auto - { - (void)this; - return context.begin(); - } - - template - auto format(const gal::prometheus::primitive::basic_point& point, FormatContext& context) const noexcept -> auto - { - if constexpr (N == 2) { return std::format_to(context.out(), "({},{})", point.x, point.y); } - else if constexpr (N == 3) { return std::format_to(context.out(), "({},{},{})", point.x, point.y, point.z); } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - }; -} // namespace std diff --git a/src/primitive/primitive.hpp b/src/primitive/primitive.hpp new file mode 100644 index 00000000..ff85d026 --- /dev/null +++ b/src/primitive/primitive.hpp @@ -0,0 +1,14 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include diff --git a/src/primitive/primitive.ixx b/src/primitive/primitive.ixx deleted file mode 100644 index 606c653d..00000000 --- a/src/primitive/primitive.ixx +++ /dev/null @@ -1,30 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -export module gal.prometheus.primitive; - -export import :multidimensional; -export import :point; -export import :extent; -export import :rect; -export import :circle; -export import :ellipse; -export import :color; -export import :vertex; - -#else -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#endif diff --git a/src/primitive/rect.hpp b/src/primitive/rect.hpp new file mode 100644 index 00000000..0bd3ce7c --- /dev/null +++ b/src/primitive/rect.hpp @@ -0,0 +1,511 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include + +#include + +#include +#include + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace gal::prometheus +{ + namespace primitive + { + template + struct basic_rect; + + template + requires (std::is_arithmetic_v and std::is_arithmetic_v) + struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_rect<2, PointValueType, ExtentValueType> final : meta::dimension> + { + using point_value_type = PointValueType; + using extent_value_type = ExtentValueType; + + using point_type = basic_point<2, point_value_type>; + using extent_type = basic_extent<2, extent_value_type>; + + template + requires (Index < 2) + using element_type = std::conditional_t; + + point_type point; + extent_type extent; + + // warning: missing initializer for member ‘gal::prometheus::primitive::basic_rect<2, float>::’ [-Wmissing-field-initializers] + // {.point = point, .extent = extent}; + // ^ + // No initialization value specified for base class `meta::dimension` + constexpr basic_rect() noexcept + : point{}, + extent{} {} + + constexpr basic_rect(const point_type point, const extent_type extent) noexcept + : point{point}, + extent{extent} {} + + constexpr basic_rect(const point_type left_top, const point_type right_bottom) noexcept + : basic_rect + { + left_top, + static_cast(right_bottom.x - left_top.x), + static_cast(right_bottom.y - left_top.y) + } {} + + constexpr basic_rect( + const point_type point, + const extent_value_type width, + const extent_value_type height + ) noexcept + : point{point}, + extent{width, height} {} + + constexpr basic_rect( + const point_value_type x, + const point_value_type y, + const extent_type extent + ) noexcept + : point{x, y}, + extent{extent} {} + + constexpr basic_rect( + const point_value_type x, + const point_value_type y, + const extent_value_type width, + const extent_value_type height + ) noexcept + : point{x, y}, + extent{width, height} {} + + template + requires(Index < 2) + [[nodiscard]] constexpr auto get() const noexcept -> const element_type& + { + if constexpr (Index == 0) { return point; } + else if constexpr (Index == 1) { return extent; } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template + requires(Index < 2) + [[nodiscard]] constexpr auto get() noexcept -> element_type& + { + if constexpr (Index == 0) { return point; } + else if constexpr (Index == 1) { return extent; } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + [[nodiscard]] constexpr explicit operator basic_rect<3, point_value_type, extent_value_type>() const noexcept + { + using r = basic_rect<3, point_value_type, extent_value_type>; + return {.point = point.operator typename r::point_type(), .extent = extent.operator typename r::extent_type()}; + } + + [[nodiscard]] constexpr auto left_top() const noexcept -> point_type + { + return point; + } + + [[nodiscard]] constexpr auto left_bottom() const noexcept -> point_type + { + return {point.x, point.y + extent.height}; + } + + [[nodiscard]] constexpr auto right_top() const noexcept -> point_type + { + return {point.x + extent.width, point.y}; + } + + [[nodiscard]] constexpr auto right_bottom() const noexcept -> point_type + { + return {point.x + extent.width, point.y + extent.height}; + } + + [[nodiscard]] constexpr auto center() const noexcept -> point_type + { + return {point.x + width() / 2, point.y + height() / 2}; + } + + [[nodiscard]] constexpr auto empty() const noexcept -> bool + { + return extent.width == 0 or extent.height == 0; + } + + [[nodiscard]] constexpr auto valid() const noexcept -> bool + { + return extent.width >= 0 and extent.height >= 0; + } + + [[nodiscard]] constexpr auto width() const noexcept -> extent_value_type + { + return extent.width; + } + + [[nodiscard]] constexpr auto height() const noexcept -> extent_value_type + { + return extent.height; + } + + [[nodiscard]] constexpr auto size() const noexcept -> extent_type + { + return {width(), height()}; + } + + [[nodiscard]] constexpr auto includes(const point_type& p) const noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); + + return p.between(left_top(), right_bottom()); + } + + [[nodiscard]] constexpr auto includes(const basic_rect& rect) const noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size().exact_greater_than(rect.size())); + + return + rect.point.x >= point.x and + rect.point.x + rect.width() < point.x + width() and + rect.point.y >= point.y and + rect.point.y + rect.height() < point.y + height(); + } + + [[nodiscard]] constexpr auto intersects(const basic_rect& rect) const noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); + + return not( + rect.point.x >= point.x + width() or + rect.point.x + rect.width() <= point.x or + rect.point.y >= point.y + height() or + rect.point.y + rect.height() <= point.y + ); + } + + [[nodiscard]] constexpr auto combine_max(const basic_rect& rect) const noexcept -> basic_rect + { + return + { + std::ranges::min(point.x, rect.point.x), + std::ranges::min(point.y, rect.point.y), + std::ranges::max(point.x + width(), rect.point.x + rect.width()), + std::ranges::max(point.y + height(), rect.point.y + rect.height()) + }; + } + + [[nodiscard]] constexpr auto combine_min(const basic_rect& rect) const noexcept -> basic_rect + { + return + { + std::ranges::max(point.x, rect.point.x), + std::ranges::max(point.y, rect.point.y), + std::ranges::min(point.x + width(), rect.point.x + rect.width()), + std::ranges::min(point.y + height(), rect.point.y + rect.height()) + }; + } + }; + + template + requires (std::is_arithmetic_v and std::is_arithmetic_v) + struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_rect<3, PointValueType, ExtentValueType> final : meta::dimension> + { + using point_value_type = PointValueType; + using extent_value_type = ExtentValueType; + + using point_type = basic_point<3, point_value_type>; + using extent_type = basic_extent<3, extent_value_type>; + + template + requires (Index < 2) + using element_type = std::conditional_t; + + point_type point; + extent_type extent; + + // warning: missing initializer for member ‘gal::prometheus::primitive::basic_rect<3, float>::’ [-Wmissing-field-initializers] + // {.point = point, .extent = extent}; + // ^ + // No initialization value specified for base class `meta::dimension` + constexpr basic_rect() noexcept + : point{}, + extent{} {} + + constexpr basic_rect(const point_type point, const extent_type extent) noexcept + : point{point}, + extent{extent} {} + + constexpr basic_rect(const point_type left_top_near, const point_type right_bottom_far) noexcept + : basic_rect + { + left_top_near, + static_cast(right_bottom_far.x - left_top_near.x), + static_cast(right_bottom_far.y - left_top_near.y), + static_cast(right_bottom_far.z - left_top_near.z) + } {} + + constexpr basic_rect( + const point_type point, + const extent_value_type width, + const extent_value_type height, + const extent_value_type depth + ) noexcept + : point{point}, + extent{width, height, depth} {} + + constexpr basic_rect( + const point_value_type x, + const point_value_type y, + const point_value_type z, + const extent_type extent + ) noexcept + : point{x, y, z}, + extent{extent} {} + + constexpr basic_rect( + const point_value_type x, + const point_value_type y, + const point_value_type z, + const extent_value_type width, + const extent_value_type height, + const extent_value_type depth + ) noexcept + : point{x, y, z}, + extent{width, height, depth} {} + + template + requires(Index < 2) + [[nodiscard]] constexpr auto get() const noexcept -> const element_type& + { + if constexpr (Index == 0) { return point; } + else if constexpr (Index == 1) { return extent; } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + template + requires(Index < 2) + [[nodiscard]] constexpr auto get() noexcept -> element_type& + { + if constexpr (Index == 0) { return point; } + else if constexpr (Index == 1) { return extent; } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + + [[nodiscard]] constexpr explicit operator basic_rect<2, point_value_type, extent_value_type>() const noexcept + { + using r = basic_rect<2, point_value_type, extent_value_type>; + return {.point = point.operator typename r::point_type(), .extent = extent.operator typename r::extent_type()}; + } + + [[nodiscard]] constexpr auto left_top_near() const noexcept -> point_type + { + return point; + } + + [[nodiscard]] constexpr auto left_bottom_near() const noexcept -> point_type + { + return {point.x, point.y + extent.height, point.z}; + } + + [[nodiscard]] constexpr auto left_top_far() const noexcept -> point_type + { + return {point.x, point.y, point.z + extent.depth}; + } + + [[nodiscard]] constexpr auto left_bottom_far() const noexcept -> point_type + { + return {point.x, point.y + extent.height, point.z + extent.depth}; + } + + [[nodiscard]] constexpr auto right_top_near() const noexcept -> point_type + { + return {point.x + extent.width, point.y, point.z}; + } + + [[nodiscard]] constexpr auto right_bottom_near() const noexcept -> point_type + { + return {point.x + extent.width, point.y + extent.height, point.z}; + } + + [[nodiscard]] constexpr auto right_top_far() const noexcept -> point_type + { + return {point.x + extent.width, point.y, point.z + extent.depth}; + } + + [[nodiscard]] constexpr auto right_bottom_far() const noexcept -> point_type + { + return {point.x + extent.width, point.y + extent.height, point.z + extent.depth}; + } + + [[nodiscard]] constexpr auto center() const noexcept -> point_type + { + return {point.x + width() / 2, point.y + height() / 2, point.z + depth()}; + } + + [[nodiscard]] constexpr auto empty() const noexcept -> bool + { + return extent.width == 0 or extent.height == 0 or extent.depth == 0; + } + + [[nodiscard]] constexpr auto valid() const noexcept -> bool + { + return extent.width >= 0 and extent.height >= 0 and extent.depth >= 0; + } + + [[nodiscard]] constexpr auto width() const noexcept -> extent_value_type + { + return extent.width; + } + + [[nodiscard]] constexpr auto height() const noexcept -> extent_value_type + { + return extent.height; + } + + [[nodiscard]] constexpr auto depth() const noexcept -> extent_value_type + { + return extent.depth; + } + + [[nodiscard]] constexpr auto size() const noexcept -> extent_type + { + return {width(), height(), depth()}; + } + + [[nodiscard]] constexpr auto includes(const point_type& p) const noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); + + return p.between(left_top_near(), right_bottom_near()); + } + + [[nodiscard]] constexpr auto includes(const basic_rect& rect) const noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size().exact_greater_than(rect.size())); + + return + rect.point.x >= point.x and + rect.point.x + rect.width() < point.x + width() and + rect.point.y >= point.y and + rect.point.y + rect.height() < point.y + height() and + rect.point.z >= point.z and + rect.point.z + rect.depth() < point.z + depth(); + } + + [[nodiscard]] constexpr auto intersects(const basic_rect& rect) const noexcept -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); + + return not( + rect.point.x >= point.x + width() or + rect.point.x + rect.width() <= point.x or + rect.point.y >= point.y + height() or + rect.point.y + rect.height() <= point.y or + rect.point.z >= point.z + depth() or + rect.point.z + rect.depth() <= point.z + ); + } + + [[nodiscard]] constexpr auto combine_max(const basic_rect& rect) const noexcept -> basic_rect + { + return + { + std::ranges::min(point.x, rect.point.x), + std::ranges::min(point.y, rect.point.y), + std::ranges::min(point.z, rect.point.z), + std::ranges::max(point.x + width(), rect.point.x + rect.width()), + std::ranges::max(point.y + height(), rect.point.y + rect.height()), + std::ranges::max(point.z + depth(), rect.point.z + rect.depth()) + }; + } + + [[nodiscard]] constexpr auto combine_min(const basic_rect& rect) const noexcept -> basic_rect + { + return + { + std::ranges::max(point.x, rect.point.x), + std::ranges::max(point.y, rect.point.y), + std::ranges::max(point.z, rect.point.z), + std::ranges::min(point.x + width(), rect.point.x + rect.width()), + std::ranges::min(point.y + height(), rect.point.y + rect.height()), + std::ranges::min(point.z + depth(), rect.point.z + rect.depth()) + }; + } + }; + + template + using basic_rect_2d = basic_rect<2, PointValueType, ExtentValueType>; + + template + using basic_rect_3d = basic_rect<3, PointValueType, ExtentValueType>; + } + + namespace meta + { + // This makes `rect1 == rect2` return a boolean instead of array + template + struct dimension_folder, DimensionFoldOperation::EQUAL> + { + constexpr static auto value = DimensionFoldCategory::ALL; + }; + + // This makes `rect1 != rect2` return a boolean instead of array + template + struct dimension_folder, DimensionFoldOperation::NOT_EQUAL> + { + constexpr static auto value = DimensionFoldCategory::ANY; + }; + } +} + +namespace std +{ + template + struct + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + [[msvc::known_semantics]] + #endif + tuple_element> // NOLINT(cert-dcl58-cpp) + { + using type = typename gal::prometheus::primitive::basic_rect::template element_type; + }; + + template + struct tuple_size> // NOLINT(cert-dcl58-cpp) + : std::integral_constant {}; + + template + struct formatter> // NOLINT(cert-dcl58-cpp) + { + template + constexpr auto parse(ParseContext& context) const noexcept -> auto + { + (void)this; + return context.begin(); + } + + template + auto format(const gal::prometheus::primitive::basic_rect& rect, FormatContext& context) const noexcept -> auto + { + return std::format_to( + context.out(), + "{}{}", + rect.point, + rect.extent + ); + } + }; +} diff --git a/src/primitive/rect.ixx b/src/primitive/rect.ixx deleted file mode 100644 index 0a88e1ae..00000000 --- a/src/primitive/rect.ixx +++ /dev/null @@ -1,644 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.primitive:rect; - -import std; -import gal.prometheus.meta; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -import :multidimensional; -import :point; -import :extent; - -#else -#pragma once - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -#endif - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::primitive) -{ - template - requires (std::is_arithmetic_v and std::is_arithmetic_v) - struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_rect; - - template - struct is_basic_rect : std::false_type {}; - - template - struct is_basic_rect> : std::true_type {}; - - template - constexpr auto is_basic_rect_v = is_basic_rect::value; - - template - concept basic_rect_t = is_basic_rect_v; - - template - struct is_rect_compatible : std::false_type {}; - - // point + extent - template> OtherType> - requires (meta::member_size() == 2) - struct is_rect_compatible> : - std::bool_constant< - point_compatible_t, basic_point<2, PointValueType>> and - extent_compatible_t, basic_extent<2, ExtentValueType>> - > {}; - - // point + extent - template> OtherType> - requires(meta::member_size() == 2) - struct is_rect_compatible> : - std::bool_constant< - point_compatible_t, basic_point<3, PointValueType>> and - extent_compatible_t, basic_extent<3, ExtentValueType>> - > {}; - - // left + top + right + bottom - template> OtherType> - requires(meta::member_size() == 4) - struct is_rect_compatible> : - std::bool_constant< - std::convertible_to, PointValueType> and - std::convertible_to, PointValueType> and - std::convertible_to, ExtentValueType> and - std::convertible_to, ExtentValueType> - > {}; - - // left + top + near + right + bottom + far - template> OtherType> - requires(meta::member_size() == 6) - struct is_rect_compatible> : - std::bool_constant< - std::convertible_to, PointValueType> and - std::convertible_to, PointValueType> and - std::convertible_to, PointValueType> and - std::convertible_to, ExtentValueType> and - std::convertible_to, ExtentValueType> and - std::convertible_to, ExtentValueType> - > {}; - - template - constexpr auto is_rect_compatible_v = is_rect_compatible::value; - - template - concept rect_compatible_t = is_rect_compatible_v; - - template - [[nodiscard]] constexpr auto operator==( - const basic_rect& lhs, - const basic_rect& rhs - ) noexcept -> bool - { - return lhs.point == rhs.point and lhs.extent == rhs.extent; - } - - template> R> - [[nodiscard]] constexpr auto operator==(const basic_rect& lhs, const R& rhs) noexcept -> bool - { - if constexpr (N == 2) - { - if constexpr (meta::member_size() == 2) - { - // point.ixx => operator== - // extent.ixx => operator== - return lhs.point == meta::member_of_index<0>(rhs) and lhs.extent == meta::member_of_index<1>(rhs); - } - else if constexpr (meta::member_size() == 4) - { - const auto x = meta::member_of_index<0>(rhs); - const auto y = meta::member_of_index<1>(rhs); - const auto width = meta::member_of_index<2>(rhs); - const auto height = meta::member_of_index<3>(rhs); - - return x == lhs.point.x and y == lhs.point.y and width == lhs.extent.width and height = lhs.extent.height; - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - } - else if constexpr (N == 3) - { - if constexpr (meta::member_size() == 2) - { - // point.ixx => operator== - // extent.ixx => operator== - return lhs.point == meta::member_of_index<0>(rhs) and lhs.extent == meta::member_of_index<1>(rhs); - } - else if constexpr (meta::member_size() == 6) - { - const auto x = meta::member_of_index<0>(rhs); - const auto y = meta::member_of_index<1>(rhs); - const auto z = meta::member_of_index<2>(rhs); - const auto width = meta::member_of_index<4>(rhs); - const auto height = meta::member_of_index<5>(rhs); - const auto depth = meta::member_of_index<6>(rhs); - - return - x == lhs.point.x and y == lhs.point.y and z == lhs.point.z and - width == lhs.extent.width and height = lhs.extent.height and depth == lhs.extent.depth; - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - } - else - { - GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); - } - } - - template> L> - [[nodiscard]] constexpr auto operator==(const L& lhs, const basic_rect& rhs) noexcept -> bool - { - return rhs == lhs; - } - - template - requires(std::is_arithmetic_v and std::is_arithmetic_v) - struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_rect<2, PointValueType, ExtentValueType> final - : multidimensional, PointValueType, ExtentValueType> - { - using point_value_type = PointValueType; - using extent_value_type = ExtentValueType; - - using point_type = basic_point<2, point_value_type>; - using extent_type = basic_extent<2, extent_value_type>; - - constexpr static auto is_always_equal = true; - - constexpr static std::size_t element_size{2}; - template - requires(Index < element_size) - using element_type = std::conditional_t; - - point_type point; - extent_type extent; - - constexpr explicit(false) basic_rect(const point_value_type point_value = point_value_type{0}, const extent_value_type extent_value = extent_value_type{0}) noexcept - : point{point_value}, - extent{extent_value} {} - - constexpr basic_rect(const point_value_type left, const point_value_type top, const point_value_type right, const point_value_type bottom) noexcept - : point{left, top}, - extent{static_cast(right - left), static_cast(bottom - top)} {} - - constexpr basic_rect(const point_type& left_top, const point_type& right_bottom) noexcept - : basic_rect{left_top.x, left_top.y, right_bottom.x, right_bottom.y} {} - - constexpr basic_rect(const point_type& left_top, const extent_type& extent) noexcept - : point{left_top}, - extent{extent} {} - - template U> - constexpr explicit basic_rect(const U& value) noexcept - : basic_rect{} - { - *this = value; - } - - constexpr basic_rect(const basic_rect&) noexcept = default; - constexpr basic_rect(basic_rect&&) noexcept = default; - constexpr auto operator=(const basic_rect&) noexcept -> basic_rect& = default; - constexpr auto operator=(basic_rect&&) noexcept -> basic_rect& = default; - constexpr ~basic_rect() noexcept = default; - - template U> - constexpr auto operator=(const U& value) noexcept -> basic_rect& - { - if constexpr (meta::member_size() == 2) - { - const auto [_point, _extent] = value; - point = static_cast(_point); - extent = static_cast(_extent); - } - else if constexpr (meta::member_size() == 4) - { - const auto [_left, _top, _right, _bottom] = value; - *this = basic_rect{ - static_cast(_left), - static_cast(_top), - static_cast(_right), - static_cast(_bottom) - }; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - - return *this; - } - - template - requires(Index < 2) - [[nodiscard]] constexpr auto get() const noexcept -> std::add_lvalue_reference_t>> - { - if constexpr (Index == 0) { return point; } - else if constexpr (Index == 1) { return extent; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template - requires(Index < 2) - [[nodiscard]] constexpr auto get() noexcept -> std::add_lvalue_reference_t> - { - if constexpr (Index == 0) { return point; } - else if constexpr (Index == 1) { return extent; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - [[nodiscard]] constexpr auto left_top() const noexcept -> point_type { return point; } - - [[nodiscard]] constexpr auto left_bottom() const noexcept -> point_type { return {point.x, point.y + extent.height}; } - - [[nodiscard]] constexpr auto right_top() const noexcept -> point_type { return {point.x + extent.width, point.y}; } - - [[nodiscard]] constexpr auto right_bottom() const noexcept -> point_type { return {point.x + extent.width, point.y + extent.height}; } - - [[nodiscard]] constexpr auto center() const noexcept -> point_type { return {point.x + width() / 2, point.y + height() / 2}; } - - [[nodiscard]] constexpr auto empty() const noexcept -> bool { return extent.width == 0 or extent.height == 0; } - - [[nodiscard]] constexpr auto valid() const noexcept -> bool { return extent.width >= 0 and extent.height >= 0; } - - [[nodiscard]] constexpr auto width() const noexcept -> extent_value_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - return extent.width; - } - - [[nodiscard]] constexpr auto height() const noexcept -> extent_value_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(valid()); - return extent.height; - } - - [[nodiscard]] constexpr auto size() const noexcept -> extent_type { return {width(), height()}; } - - [[nodiscard]] constexpr auto includes(const point_type& p) const noexcept -> bool - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); - - return p.template between(left_top(), right_bottom()); - } - - [[nodiscard]] constexpr auto includes(const basic_rect& rect) const noexcept -> bool - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size().exact_greater_than(rect.size())); - - return - rect.point.x >= point.x and - rect.point.x + rect.width() < point.x + width() and - rect.point.y >= point.y and - rect.point.y + rect.height() < point.y + height(); - } - - [[nodiscard]] constexpr auto intersects(const basic_rect& rect) const noexcept -> bool - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); - - return not( - rect.point.x >= point.x + width() or - rect.point.x + rect.width() <= point.x or - rect.point.y >= point.y + height() or - rect.point.y + rect.height() <= point.y - ); - } - - [[nodiscard]] constexpr auto combine_max(const basic_rect& rect) const noexcept -> basic_rect - { - return - { - std::ranges::min(point.x, rect.point.x), - std::ranges::min(point.y, rect.point.y), - std::ranges::max(point.x + width(), rect.point.x + rect.width()), - std::ranges::max(point.y + height(), rect.point.y + rect.height()) - }; - } - - [[nodiscard]] constexpr auto combine_min(const basic_rect& rect) const noexcept -> basic_rect - { - return - { - std::ranges::max(point.x, rect.point.x), - std::ranges::max(point.y, rect.point.y), - std::ranges::min(point.x + width(), rect.point.x + rect.width()), - std::ranges::min(point.y + height(), rect.point.y + rect.height()) - }; - } - }; - - template - requires(std::is_arithmetic_v and std::is_arithmetic_v) - struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_rect<3, PointValueType, ExtentValueType> final - : multidimensional, PointValueType, ExtentValueType> - { - using point_value_type = PointValueType; - using extent_value_type = ExtentValueType; - - using point_type = basic_point<3, point_value_type>; - using extent_type = basic_extent<3, extent_value_type>; - - constexpr static auto is_always_equal = true; - - constexpr static std::size_t element_size{2}; - template - requires(Index < element_size) - using element_type = std::conditional_t; - - point_type point; - extent_type extent; - - constexpr explicit(false) basic_rect(const point_value_type point_value = point_value_type{0}, const extent_value_type extent_value = extent_value_type{0}) noexcept - : point{point_value}, - extent{extent_value} {} - - // fixme: ICE here - #if defined(GAL_PROMETHEUS_COMPILER_MSVC) - #pragma push_macro("near") - #pragma push_macro("far") - #undef near - #undef far - #endif - - constexpr basic_rect( - const point_value_type left, - const point_value_type top, - const point_value_type near, - const point_value_type right, - const point_value_type bottom, - const point_value_type far - ) noexcept - : point{left, top, near}, - extent{static_cast(right - left), static_cast(bottom - top), static_cast(far - near)} {} - - constexpr basic_rect(const point_type& left_top_near, const point_type& right_bottom_far) noexcept - : basic_rect{left_top_near.x, left_top_near.y, left_top_near.z, right_bottom_far.x, right_bottom_far.y, right_bottom_far.z} {} - - constexpr basic_rect(const point_type& left_top_near, const extent_type& extent) noexcept - : point{left_top_near}, - extent{extent} {} - - template U> - constexpr explicit basic_rect(const U& value) noexcept - : basic_rect{} - { - *this = value; - } - - constexpr basic_rect(const basic_rect&) noexcept = default; - constexpr basic_rect(basic_rect&&) noexcept = default; - constexpr auto operator=(const basic_rect&) noexcept -> basic_rect& = default; - constexpr auto operator=(basic_rect&&) noexcept -> basic_rect& = default; - constexpr ~basic_rect() noexcept = default; - - template U> - constexpr auto operator=(const U& value) noexcept -> basic_rect& - { - if constexpr (meta::member_size() == 2) - { - const auto [_point, _extent] = value; - point = static_cast(_point); - extent = static_cast(_extent); - } - else if constexpr (meta::member_size() == 6) - { - const auto [_left, _top, _near, _right, _bottom, _far] = value; - *this = basic_rect{ - static_cast(_left), - static_cast(_top), - static_cast(_near), - static_cast(_right), - static_cast(_bottom), - static_cast(_far) - }; - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - - return *this; - } - - #if defined(GAL_PROMETHEUS_COMPILER_MSVC) - #pragma pop_macro("far") - #pragma pop_macro("near") - #endif - - template - requires(Index < 2) - [[nodiscard]] constexpr auto get() const noexcept -> std::add_lvalue_reference_t>> - { - if constexpr (Index == 0) { return point; } - else if constexpr (Index == 1) { return extent; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - template - requires(Index < 2) - [[nodiscard]] constexpr auto get() noexcept -> std::add_lvalue_reference_t> - { - if constexpr (Index == 0) { return point; } - else if constexpr (Index == 1) { return extent; } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - - [[nodiscard]] constexpr auto left_top_near() const noexcept -> point_type // - { - return point; - } - - [[nodiscard]] constexpr auto left_bottom_near() const noexcept -> point_type // - { - return {point.x, point.y + extent.height, point.z}; - } - - [[nodiscard]] constexpr auto left_top_far() const noexcept -> point_type // - { - return {point.x, point.y, point.z + extent.depth}; - } - - [[nodiscard]] constexpr auto left_bottom_far() const noexcept -> point_type // - { - return {point.x, point.y + extent.height, point.z + extent.depth}; - } - - [[nodiscard]] constexpr auto right_top_near() const noexcept -> point_type // - { - return {point.x + extent.width, point.y, point.z}; - } - - [[nodiscard]] constexpr auto right_bottom_near() const noexcept -> point_type // - { - return {point.x + extent.width, point.y + extent.height, point.z}; - } - - [[nodiscard]] constexpr auto right_top_far() const noexcept -> point_type // - { - return {point.x + extent.width, point.y, point.z + extent.depth}; - } - - [[nodiscard]] constexpr auto right_bottom_far() const noexcept -> point_type // - { - return {point.x + extent.width, point.y + extent.height, point.z + extent.depth}; - } - - [[nodiscard]] constexpr auto center() const noexcept -> point_type - { - return {point.x + width() / 2, point.y + height() / 2, point.z + depth()}; - } - - [[nodiscard]] constexpr auto empty() const noexcept -> bool { return extent.width == 0 or extent.height == 0 or extent.depth == 0; } - - [[nodiscard]] constexpr auto valid() const noexcept -> bool { return extent.width >= 0 and extent.height >= 0 and extent.depth >= 0; } - - [[nodiscard]] constexpr auto width() const noexcept -> extent_value_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); - return extent.width; - } - - [[nodiscard]] constexpr auto height() const noexcept -> extent_value_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); - return extent.height; - } - - [[nodiscard]] constexpr auto depth() const noexcept -> extent_value_type - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); - return extent.depth; - } - - [[nodiscard]] constexpr auto size() const noexcept -> extent_type { return {width(), height()}; } - - [[nodiscard]] constexpr auto includes(const point_type& p) const noexcept -> bool - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); - - return p.template between(left_top_near(), right_bottom_near()); - } - - [[nodiscard]] constexpr auto includes(const basic_rect& rect) const noexcept -> bool - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(size().exact_greater_than(rect.size())); - - return - rect.point.x >= point.x and - rect.point.x + rect.width() < point.x + width() and - rect.point.y >= point.y and - rect.point.y + rect.height() < point.y + height() and - rect.point.z >= point.z and - rect.point.z + rect.depth() < point.z + depth(); - } - - [[nodiscard]] constexpr auto intersects(const basic_rect& rect) const noexcept -> bool - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not empty() and valid()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not rect.empty() and rect.valid()); - - return not( - rect.point.x >= point.x + width() or - rect.point.x + rect.width() <= point.x or - rect.point.y >= point.y + height() or - rect.point.y + rect.height() <= point.y or - rect.point.z >= point.z + depth() or - rect.point.z + rect.depth() <= point.z - ); - } - - [[nodiscard]] constexpr auto combine_max(const basic_rect& rect) const noexcept -> basic_rect - { - return - { - std::ranges::min(point.x, rect.point.x), - std::ranges::min(point.y, rect.point.y), - std::ranges::min(point.z, rect.point.z), - std::ranges::max(point.x + width(), rect.point.x + rect.width()), - std::ranges::max(point.y + height(), rect.point.y + rect.height()), - std::ranges::max(point.z + depth(), rect.point.z + rect.depth()) - }; - } - - [[nodiscard]] constexpr auto combine_min(const basic_rect& rect) const noexcept -> basic_rect - { - return - { - std::ranges::max(point.x, rect.point.x), - std::ranges::max(point.y, rect.point.y), - std::ranges::max(point.z, rect.point.z), - std::ranges::min(point.x + width(), rect.point.x + rect.width()), - std::ranges::min(point.y + height(), rect.point.y + rect.height()), - std::ranges::min(point.z + depth(), rect.point.z + rect.depth()) - }; - } - }; - - template - using basic_rect_2d = basic_rect<2, PointValueType, ExtentValueType>; - - template - using basic_rect_3d = basic_rect<3, PointValueType, ExtentValueType>; -} - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE_STD -{ - template - struct - #if defined(GAL_PROMETHEUS_COMPILER_MSVC) - [[msvc::known_semantics]] - #endif - tuple_element> // NOLINT(cert-dcl58-cpp) - { - using type = typename gal::prometheus::primitive::basic_rect::template element_type; - }; - - template - struct tuple_size> // NOLINT(cert-dcl58-cpp) - : std::integral_constant::element_size> {}; - - template - struct formatter> // NOLINT(cert-dcl58-cpp) - { - template - constexpr auto parse(ParseContext& context) const noexcept -> auto - { - (void)this; - return context.begin(); - } - - template - auto format(const gal::prometheus::primitive::basic_rect& rect, FormatContext& context) const noexcept -> auto - { - return std::format_to( - context.out(), - "[{}-{}]", - rect.point, - rect.extent - ); - } - }; -} diff --git a/src/primitive/vertex.ixx b/src/primitive/vertex.hpp similarity index 53% rename from src/primitive/vertex.ixx rename to src/primitive/vertex.hpp index 7b185634..de113e64 100644 --- a/src/primitive/vertex.ixx +++ b/src/primitive/vertex.hpp @@ -1,20 +1,8 @@ // This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal +// Copyright (C) 2022-2025 Life4gal // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.primitive:vertex; - -import std; -import :point; -import :color; - -#else #pragma once #include @@ -22,42 +10,14 @@ import :color; #include #include -#include -#include -#endif +#include +#include -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::primitive) +namespace gal::prometheus::primitive { - template< - typename, // point type - typename, // uv type - typename // color type - > - struct basic_vertex; - - template - struct is_basic_vertex : std::false_type {}; - - template< - typename PositionType, - typename UvType, - typename ColorType - > - struct is_basic_vertex> : std::true_type {}; - - template - constexpr auto is_basic_vertex_v = is_basic_vertex::value; - - template - concept basic_vertex_t = is_basic_vertex_v; - - template< - basic_point_t PositionType, - basic_point_t UvType, - basic_color_t ColorType - > - struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_vertex final + template + struct basic_vertex final { using position_type = PositionType; using uv_type = UvType; @@ -67,29 +27,16 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::primitive) using uv_value_type = typename uv_type::value_type; using color_value_type = typename color_type::value_type; - constexpr static std::size_t element_size{3}; template - requires(Index < element_size) using element_type = std::conditional_t>; position_type position; uv_type uv; color_type color; - constexpr basic_vertex(const position_type position, const uv_type uv, const color_type color) noexcept - : position{position}, - uv{uv}, - color{color} {} - - constexpr basic_vertex(const position_type position, const color_type color) noexcept - : basic_vertex{position, {}, color} {} - - constexpr basic_vertex() noexcept - : basic_vertex{{}, {}, {}} {} - template - requires(Index < element_size) - [[nodiscard]] constexpr auto get() const noexcept -> std::add_lvalue_reference_t>> + requires(Index < 3) + [[nodiscard]] constexpr auto get() const noexcept -> const element_type& { if constexpr (Index == 0) { return position; } else if constexpr (Index == 1) { return uv; } @@ -98,8 +45,8 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::primitive) } template - requires(Index < element_size) - [[nodiscard]] constexpr auto get() noexcept -> std::add_lvalue_reference_t> + requires(Index < 3) + [[nodiscard]] constexpr auto get() noexcept -> element_type& { if constexpr (Index == 0) { return position; } else if constexpr (Index == 1) { return uv; } @@ -108,11 +55,8 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::primitive) } }; - template< - basic_point_t PositionType, - basic_color_t ColorType - > - struct [[nodiscard]] GAL_PROMETHEUS_COMPILER_EMPTY_BASE basic_vertex final + template + struct basic_vertex final { using position_type = PositionType; using color_type = ColorType; @@ -120,22 +64,15 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::primitive) using position_value_type = typename position_type::value_type; using color_value_type = typename color_type::value_type; - constexpr static std::size_t element_size{2}; + template + using element_type = std::conditional_t; position_type position; color_type color; - constexpr basic_vertex(const position_type position, const color_type color) noexcept - : position{position}, - color{color} {} - template - requires(Index < element_size) - [[nodiscard]] constexpr auto get() const noexcept - -> std::conditional_t< - Index == 0, - const position_type&, - const color_type&> + requires(Index < 2) + [[nodiscard]] constexpr auto get() const noexcept -> const element_type& { if constexpr (Index == 0) { return position; } else if constexpr (Index == 1) { return color; } @@ -143,12 +80,8 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::primitive) } template - requires(Index < element_size) - [[nodiscard]] constexpr auto get() noexcept - -> std::conditional_t< - Index == 0, - position_type&, - color_type&> + requires(Index < 2) + [[nodiscard]] constexpr auto get() noexcept -> element_type& { if constexpr (Index == 0) { return position; } else if constexpr (Index == 1) { return color; } @@ -157,7 +90,7 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::primitive) }; } -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE_STD +namespace std { template struct @@ -171,7 +104,7 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE_STD template struct tuple_size> // NOLINT(cert-dcl58-cpp) - : std::integral_constant::element_size> {}; + : std::integral_constant ? 0 : 1> {}; template struct formatter> // NOLINT(cert-dcl58-cpp) @@ -189,11 +122,13 @@ GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE_STD FormatContext& context ) const noexcept -> auto { - if constexpr (gal::prometheus::primitive::basic_vertex::element_size == 2) + using vertex_type = gal::prometheus::primitive::basic_vertex; + + if constexpr (std::tuple_size_v == 2) { return std::format_to(context.out(), "[pos{}color{}]", vertex.position, vertex.color); } - else if constexpr (gal::prometheus::primitive::basic_vertex::element_size == 3) + else if constexpr (std::tuple_size_v == 3) { return std::format_to(context.out(), "[pos{}uv{}color{}]", vertex.position, vertex.uv, vertex.color); } diff --git a/src/prometheus.ixx b/src/prometheus.ixx deleted file mode 100644 index d4d3a0fe..00000000 --- a/src/prometheus.ixx +++ /dev/null @@ -1,39 +0,0 @@ -#if GAL_PROMETHEUS_USE_MODULE -export module gal.prometheus; - -export import gal.prometheus.chars; -export import gal.prometheus.command_line_parser; -export import gal.prometheus.concurrency; -export import gal.prometheus.coroutine; -export import gal.prometheus.error; -export import gal.prometheus.functional; -export import gal.prometheus.gui; -export import gal.prometheus.memory; -export import gal.prometheus.meta; -export import gal.prometheus.numeric; -export import gal.prometheus.primitive; -export import gal.prometheus.state_machine; -export import gal.prometheus.string; -export import gal.prometheus.unit_test; -export import gal.prometheus.wildcard; - -#else -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#endif diff --git a/src/prometheus/macro.hpp b/src/prometheus/macro.hpp index 94ed55bb..d4b8f5c4 100644 --- a/src/prometheus/macro.hpp +++ b/src/prometheus/macro.hpp @@ -1,10 +1,15 @@ // This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal +// Copyright (C) 2022-2025 Life4gal // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. #pragma once +// __cpp_lib_xxx +#include +// std::unreachable() +#include + // ========================================================= // COMPILER // ========================================================= @@ -16,6 +21,11 @@ #endif #if defined(GAL_PROMETHEUS_COMPILER_MSVC) +#if __cpp_lib_unreachable >= 202202L +#define GAL_PROMETHEUS_COMPILER_UNREACHABLE() std::unreachable() +#else +#define GAL_PROMETHEUS_COMPILER_UNREACHABLE() __assume(0) +#endif #define GAL_PROMETHEUS_COMPILER_DEBUG_TRAP() __debugbreak() #define GAL_PROMETHEUS_COMPILER_IMPORTED_SYMBOL __declspec(dllimport) #define GAL_PROMETHEUS_COMPILER_EXPORTED_SYMBOL __declspec(dllexport) @@ -24,8 +34,13 @@ #define GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH __pragma(warning(push)) #define GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP __pragma(warning(pop)) #define GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(warningNumber) __pragma(warning(disable \ - : warningNumber)) + : warningNumber)) // NOLINT(bugprone-macro-parentheses) #elif defined(GAL_PROMETHEUS_COMPILER_GNU) +#if __cpp_lib_unreachable >= 202202L +#define GAL_PROMETHEUS_COMPILER_UNREACHABLE() std::unreachable() +#else +#define GAL_PROMETHEUS_COMPILER_UNREACHABLE() __builtin_unreachable() +#endif #define GAL_PROMETHEUS_COMPILER_DEBUG_TRAP() __builtin_trap() #define GAL_PROMETHEUS_COMPILER_IMPORTED_SYMBOL __attribute__((visibility("default"))) #define GAL_PROMETHEUS_COMPILER_EXPORTED_SYMBOL __attribute__((visibility("default"))) @@ -37,6 +52,11 @@ #define GAL_PROMETHEUS_COMPILER_PRIVATE_DO_PRAGMA(X) _Pragma(#X) #define GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(warningName) GAL_PROMETHEUS_COMPILER_PRIVATE_DO_PRAGMA(GCC diagnostic ignored #warningName) #elif defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) +#if __cpp_lib_unreachable >= 202202L +#define GAL_PROMETHEUS_COMPILER_UNREACHABLE() std::unreachable() +#else +#define GAL_PROMETHEUS_COMPILER_UNREACHABLE() __builtin_unreachable() +#endif #define GAL_PROMETHEUS_COMPILER_DEBUG_TRAP() __builtin_trap() #define GAL_PROMETHEUS_COMPILER_IMPORTED_SYMBOL __attribute__((visibility("default"))) #define GAL_PROMETHEUS_COMPILER_EXPORTED_SYMBOL __attribute__((visibility("default"))) @@ -48,6 +68,7 @@ #define GAL_PROMETHEUS_COMPILER_PRIVATE_DO_PRAGMA(X) _Pragma(#X) #define GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(warningName) GAL_PROMETHEUS_COMPILER_PRIVATE_DO_PRAGMA(clang diagnostic ignored #warningName) #else +#define GAL_PROMETHEUS_COMPILER_UNREACHABLE() std::unreachable() #define GAL_PROMETHEUS_COMPILER_DEBUG_TRAP() #define GAL_PROMETHEUS_COMPILER_IMPORTED_SYMBOL #define GAL_PROMETHEUS_COMPILER_EXPORTED_SYMBOL @@ -58,6 +79,24 @@ #define GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(warningName) #endif +#if defined(GAL_PROMETHEUS_COMPILER_MSVC) +#define GAL_PROMETHEUS_COMPILER_DISABLE_MSVC_WARNING(warningNumber) GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(warningNumber) +#else +#define GAL_PROMETHEUS_COMPILER_DISABLE_MSVC_WARNING(warningNumber) +#endif + +#if defined(GAL_PROMETHEUS_COMPILER_GNU) +#define GAL_PROMETHEUS_COMPILER_DISABLE_GNU_WARNING(warningName) GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(warningName) +#else +#define GAL_PROMETHEUS_COMPILER_DISABLE_GNU_WARNING(warningName) +#endif + +#if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) +#define GAL_PROMETHEUS_COMPILER_DISABLE_CLANG_WARNING(warningName) GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(warningName) +#else +#define GAL_PROMETHEUS_COMPILER_DISABLE_CLANG_WARNING(warningName) +#endif + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) #define GAL_PROMETHEUS_COMPILER_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] #elif defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) @@ -84,20 +123,25 @@ #define GAL_PROMETHEUS_COMPILER_EMPTY_BASE #endif -#if GAL_PROMETHEUS_USE_MODULE -#define GAL_PROMETHEUS_COMPILER_MODULE_INLINE -#define GAL_PROMETHEUS_COMPILER_MODULE_STATIC -#define GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN export { -#define GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END } -#define GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(n) export namespace n -#define GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE_STD export namespace std +// ---------------------------------------- +// INTELLISENSE + +// ResharperC++ +#if defined(__RESHARPER__) +#define GAL_PROMETHEUS_INTELLISENSE_RESHARPER +#endif + +// Visual Studio +#if defined(__INTELLISENSE__) +#define GAL_PROMETHEUS_INTELLISENSE_VISUAL_STUDIO +#endif + +// ---------------------------------------- + +#if defined(GAL_PROMETHEUS_INTELLISENSE_RESHARPER) or defined(GAL_PROMETHEUS_INTELLISENSE_VISUAL_STUDIO) +#define GAL_PROMETHEUS_INTELLISENSE_WORKING 1 #else -#define GAL_PROMETHEUS_COMPILER_MODULE_INLINE inline -#define GAL_PROMETHEUS_COMPILER_MODULE_STATIC static -#define GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN -#define GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -#define GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(n) namespace n -#define GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE_STD namespace std +#define GAL_PROMETHEUS_INTELLISENSE_WORKING 0 #endif // ========================================================= @@ -106,65 +150,65 @@ #define GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(...) \ []() { static_assert(AlwaysFalse, "[UNREACHABLE BRANCH]" __VA_OPT__(":\"") __VA_ARGS__ __VA_OPT__("\"")); }(); \ - std::unreachable() + GAL_PROMETHEUS_COMPILER_UNREACHABLE() -#if defined(__cpp_if_consteval) -#define GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED if consteval -#define GAL_PROMETHEUS_SEMANTIC_IF_NOT_CONSTANT_EVALUATED if not consteval -#else +// #if defined(__cpp_if_consteval) and __cpp_if_consteval >= 202106L +// #define GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED if consteval +// #define GAL_PROMETHEUS_SEMANTIC_IF_NOT_CONSTANT_EVALUATED if not consteval +// #else #define GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED if (std::is_constant_evaluated()) #define GAL_PROMETHEUS_SEMANTIC_IF_NOT_CONSTANT_EVALUATED if (not std::is_constant_evaluated()) -#endif +// #endif // fixme -#if defined(__cpp_lib_is_implicit_lifetime) +#if defined(__cpp_lib_is_implicit_lifetime) and __cpp_lib_is_implicit_lifetime >= 202302L #define GAL_PROMETHEUS_SEMANTIC_IS_IMPLICIT_LIFETIME_V(type) std::is_implicit_lifetime_v #else #define GAL_PROMETHEUS_SEMANTIC_IS_IMPLICIT_LIFETIME_V(type) std::is_standard_layout_v and std::is_trivial_v #endif -#if defined(__cpp_lib_start_lifetime_as) +#if defined(__cpp_lib_start_lifetime_as) and __cpp_lib_start_lifetime_as >= 202207L #define GAL_PROMETHEUS_SEMANTIC_START_LIFETIME_AS(type, ptr) std::start_lifetime_as(ptr) #define GAL_PROMETHEUS_SEMANTIC_START_LIFETIME_AS_ARRAY(type, ptr) std::start_lifetime_as_array(ptr) #else #define GAL_PROMETHEUS_SEMANTIC_START_LIFETIME_AS(type, ptr) \ - [](GAL_PROMETHEUS_START_LIFETIME_AS_POINTER_TYPE gal_prometheus_start_lifetime_as_p) \ - requires(std::is_trivially_copyable_v && GAL_PROMETHEUS_IS_IMPLICIT_LIFETIME_V(type)) \ - { \ - if constexpr (std::is_const_v>) \ - { \ - return reinterpret_cast>>(gal_prometheus_start_lifetime_as_p); \ - } \ - else \ - { \ - auto* gal_prometheus_start_lifetime_as_dest = gal_prometheus_start_lifetime_as_p; \ - const auto* gal_prometheus_start_lifetime_as_source = gal_prometheus_start_lifetime_as_p; \ - GAL_PROMETHEUS_DEBUG_NOT_NULL(gal_prometheus_start_lifetime_as_dest); \ - GAL_PROMETHEUS_DEBUG_NOT_NULL(gal_prometheus_start_lifetime_as_source); \ - auto* gal_prometheus_start_lifetime_as_moved = static_cast>(std::memmove(gal_prometheus_start_lifetime_as_dest, gal_prometheus_start_lifetime_as_source, sizeof(type))); \ - GAL_PROMETHEUS_DEBUG_NOT_NULL(gal_prometheus_start_lifetime_as_moved); \ - return std::launder(gal_prometheus_start_lifetime_as_moved); \ - } \ - }(ptr) -#define GAL_PROMETHEUS_SEMANTIC_START_LIFETIME_AS_ARRAY(type, ptr, size) \ - [](GAL_PROMETHEUS_START_LIFETIME_AS_POINTER_TYPE gal_prometheus_start_lifetime_as_p, const std::size_t s) \ - requires(std::is_trivially_copyable_v && GAL_PROMETHEUS_IS_IMPLICIT_LIFETIME_V(type)) \ - { \ - if constexpr (std::is_const_v>) \ - { \ - return reinterpret_cast>>(gal_prometheus_start_lifetime_as_p); \ - } \ - else \ - { \ - auto* gal_prometheus_start_lifetime_as_dest = gal_prometheus_start_lifetime_as_p; \ - const auto* gal_prometheus_start_lifetime_as_source = gal_prometheus_start_lifetime_as_p; \ - GAL_PROMETHEUS_DEBUG_NOT_NULL(gal_prometheus_start_lifetime_as_dest); \ - GAL_PROMETHEUS_DEBUG_NOT_NULL(gal_prometheus_start_lifetime_as_source); \ - auto* gal_prometheus_start_lifetime_as_moved = static_cast>(std::memmove(gal_prometheus_start_lifetime_as_dest, gal_prometheus_start_lifetime_as_source, sizeof(type) * s)); \ - GAL_PROMETHEUS_DEBUG_NOT_NULL(gal_prometheus_start_lifetime_as_moved); \ - return std::launder(gal_prometheus_start_lifetime_as_moved); \ - } \ - }(ptr, size) +[](GAL_PROMETHEUS_START_LIFETIME_AS_POINTER_TYPE gal_prometheus_start_lifetime_as_p) \ + requires(std::is_trivially_copyable_v && GAL_PROMETHEUS_IS_IMPLICIT_LIFETIME_V(type)) \ + { \ + if constexpr (std::is_const_v>) \ + { \ + return reinterpret_cast>>(gal_prometheus_start_lifetime_as_p); \ + } \ + else \ + { \ + auto* gal_prometheus_start_lifetime_as_dest = gal_prometheus_start_lifetime_as_p; \ + const auto* gal_prometheus_start_lifetime_as_source = gal_prometheus_start_lifetime_as_p; \ + GAL_PROMETHEUS_DEBUG_NOT_NULL(gal_prometheus_start_lifetime_as_dest); \ + GAL_PROMETHEUS_DEBUG_NOT_NULL(gal_prometheus_start_lifetime_as_source); \ + auto* gal_prometheus_start_lifetime_as_moved = static_cast>(std::memmove(gal_prometheus_start_lifetime_as_dest, gal_prometheus_start_lifetime_as_source, sizeof(type))); \ + GAL_PROMETHEUS_DEBUG_NOT_NULL(gal_prometheus_start_lifetime_as_moved); \ + return std::launder(gal_prometheus_start_lifetime_as_moved); \ + } \ + }(ptr) +#define GAL_PROMETHEUS_SEMANTIC_START_LIFETIME_AS_ARRAY(type, ptr, size) \ + [](GAL_PROMETHEUS_START_LIFETIME_AS_POINTER_TYPE gal_prometheus_start_lifetime_as_p, const std::size_t s) \ + requires(std::is_trivially_copyable_v && GAL_PROMETHEUS_IS_IMPLICIT_LIFETIME_V(type)) \ + { \ + if constexpr (std::is_const_v>) \ + { \ + return reinterpret_cast>>(gal_prometheus_start_lifetime_as_p); \ + } \ + else \ + { \ + auto* gal_prometheus_start_lifetime_as_dest = gal_prometheus_start_lifetime_as_p; \ + const auto* gal_prometheus_start_lifetime_as_source = gal_prometheus_start_lifetime_as_p; \ + GAL_PROMETHEUS_DEBUG_NOT_NULL(gal_prometheus_start_lifetime_as_dest); \ + GAL_PROMETHEUS_DEBUG_NOT_NULL(gal_prometheus_start_lifetime_as_source); \ + auto* gal_prometheus_start_lifetime_as_moved = static_cast>(std::memmove(gal_prometheus_start_lifetime_as_dest, gal_prometheus_start_lifetime_as_source, sizeof(type) * s)); \ + GAL_PROMETHEUS_DEBUG_NOT_NULL(gal_prometheus_start_lifetime_as_moved); \ + return std::launder(gal_prometheus_start_lifetime_as_moved); \ + } \ + }(ptr, size) #endif // fixme: UB @@ -265,20 +309,22 @@ #define GAL_PROMETHEUS_UTILITY_TO_STRING(...) GAL_PROMETHEUS_UTILITY_STRING_CAT(GAL_PROMETHEUS_UTILITY_PRIVATE_TO_STRING_, GAL_PROMETHEUS_UTILITY_ARGS_LEN(__VA_ARGS__))(__VA_ARGS__) // ========================================================= -// MODULE: gal.prometheus.meta +// MODULE: gal.prometheus.meta.string // ========================================================= #define GAL_PROMETHEUS_META_PRIVATE_DO_GENERATE_STRING_CHAR_ARRAY(string_type, this_string, string_length, begin_index) \ - [](std::index_sequence) constexpr noexcept \ - { return ::gal::prometheus::meta::string_type< \ - [](std::size_t index) constexpr noexcept \ - { \ - return (this_string)[(begin_index) + index]; \ - }(Index)...>{}; }(std::make_index_sequence{}) +[](std::index_sequence) constexpr noexcept \ + { \ + return ::gal::prometheus::meta::string_type< \ + [](std::size_t index) constexpr noexcept \ + { \ + return (this_string)[(begin_index) + index]; \ + }(Index)...>{};\ + }(std::make_index_sequence{}) #define GAL_PROMETHEUS_META_PRIVATE_STRING_CHAR_ARRAY(string_type, string) GAL_PROMETHEUS_META_PRIVATE_DO_GENERATE_STRING_CHAR_ARRAY(string_type, string, sizeof(string) / sizeof((string)[0]), 0) -#define GAL_PROMETHEUS_META_PRIVATE_STRING_CHAR_BILATERAL_ARRAY(string_type, inner_string_type, left_string, right_string) ::gal::prometheus::string::string_type -#define GAL_PROMETHEUS_META_PRIVATE_STRING_CHAR_SYMMETRY_ARRAY(string_type, inner_string_type, this_string) ::gal::prometheus::string::string_type +#define GAL_PROMETHEUS_META_PRIVATE_STRING_CHAR_BILATERAL_ARRAY(string_type, inner_string_type, left_string, right_string) ::gal::prometheus::meta::string_type +#define GAL_PROMETHEUS_META_PRIVATE_STRING_CHAR_SYMMETRY_ARRAY(string_type, inner_string_type, this_string) ::gal::prometheus::meta::string_type #define GAL_PROMETHEUS_META_STRING_CHAR_ARRAY(string) GAL_PROMETHEUS_META_PRIVATE_STRING_CHAR_ARRAY(char_array, string) #define GAL_PROMETHEUS_META_STRING_WCHAR_ARRAY(string) GAL_PROMETHEUS_META_PRIVATE_STRING_CHAR_ARRAY(wchar_array, string) @@ -290,50 +336,58 @@ #define GAL_PROMETHEUS_META_STRING_U32CHAR_ARRAY(string) GAL_PROMETHEUS_META_PRIVATE_STRING_CHAR_ARRAY(u32char_array, string) // ========================================================= -// MODULE: gal.prometheus.error +// MODULE: gal.prometheus.platform // ========================================================= -#if GAL_PROMETHEUS_USE_MODULE -#define GAL_PROMETHEUS_ERROR_IMPORT_RUNTIME_MODULE import gal.prometheus.error; - -#if GAL_PROMETHEUS_COMPILER_DEBUG -#define GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE GAL_PROMETHEUS_ERROR_IMPORT_RUNTIME_MODULE -#else -#define GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE -#endif - -#else -#define GAL_PROMETHEUS_ERROR_RUNTIME_MODULE - #if GAL_PROMETHEUS_COMPILER_DEBUG -#define GAL_PROMETHEUS_ERROR_DEBUG_MODULE GAL_PROMETHEUS_ERROR_RUNTIME_MODULE +#define GAL_PROMETHEUS_ERROR_DEBUG_MODULE // NOLINT(bugprone-macro-parentheses) #else #define GAL_PROMETHEUS_ERROR_DEBUG_MODULE #endif -#endif +// INLINE breakpoint_if_debugging +#define GAL_PROMETHEUS_ERROR_BREAKPOINT_IF(expression, message) \ + do { \ + GAL_PROMETHEUS_SEMANTIC_IF_NOT_CONSTANT_EVALUATED \ + { \ + if (static_cast(expression) and ::gal::prometheus::platform::is_debugger_present()) \ + { \ + ::gal::prometheus::platform::breakpoint_message("[" __FILE__ ":" GAL_PROMETHEUS_UTILITY_TO_STRING(__LINE__) "] -> " message); \ + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); \ + } \ + } \ + } while (false) -#define GAL_PROMETHEUS_ERROR_CALL_DEBUGGER_OR_TERMINATE(message) ::gal::prometheus::error::debug_break("[" __FILE__ ":" GAL_PROMETHEUS_UTILITY_TO_STRING(__LINE__) "] -> " message) +// INLINE breakpoint_or_terminate +#define GAL_PROMETHEUS_ERROR_BREAKPOINT_OR_TERMINATE_IF(expression, message) \ + do { \ + GAL_PROMETHEUS_SEMANTIC_IF_NOT_CONSTANT_EVALUATED \ + { \ + if (static_cast(expression)) \ + { \ + if (::gal::prometheus::platform::is_debugger_present()) \ + { \ + ::gal::prometheus::platform::breakpoint_message("[" __FILE__ ":" GAL_PROMETHEUS_UTILITY_TO_STRING(__LINE__) "] -> " message); \ + GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); \ + } \ + else \ + { \ + std::terminate(); \ + } \ + } \ + } \ + } while (false) #define GAL_PROMETHEUS_ERROR_PRIVATE_DO_CHECK(debug_type, expression, ...) \ - do { \ - GAL_PROMETHEUS_SEMANTIC_IF_NOT_CONSTANT_EVALUATED \ - { \ - if (not static_cast(expression)) \ - { \ - GAL_PROMETHEUS_ERROR_CALL_DEBUGGER_OR_TERMINATE("[" debug_type "]: \"" __VA_ARGS__ "\" --> {" GAL_PROMETHEUS_UTILITY_TO_STRING(expression) "}"); \ - GAL_PROMETHEUS_COMPILER_DEBUG_TRAP(); \ - } \ - } \ - } while (false) - -#define GAL_PROMETHEUS_ERROR_UNREACHABLE(...) std::unreachable() + GAL_PROMETHEUS_ERROR_BREAKPOINT_OR_TERMINATE_IF(not static_cast(expression), "[" debug_type "]: \"" __VA_ARGS__ "\" --> {" GAL_PROMETHEUS_UTILITY_TO_STRING(expression) "}") + +#define GAL_PROMETHEUS_ERROR_UNREACHABLE(...) GAL_PROMETHEUS_COMPILER_UNREACHABLE() #if __has_cpp_attribute(assume) #define GAL_PROMETHEUS_ERROR_ASSUME(expression, ...) \ do \ { \ - GAL_PROMETHEUS_SEMANTIC_IF_NOT_CONSTANT_EVALUATED { [[assume(expression)]]; } \ + const auto assume_expression_result = static_cast(expression); [[assume(assume_expression_result)]]; \ } while (false) #else #define GAL_PROMETHEUS_ERROR_ASSUME(expression, ...) \ @@ -366,11 +420,11 @@ { \ if constexpr (__VA_OPT__(not ) false) \ { \ - error::mob::invoke(std::format(message __VA_OPT__(, ) __VA_ARGS__)); \ + platform::mob::invoke(std::format(message __VA_OPT__(, ) __VA_ARGS__)); \ } \ else \ { \ - error::mob::invoke(message); \ + platform::mob::invoke(message); \ } \ } while (false) @@ -379,11 +433,11 @@ { \ if constexpr (__VA_OPT__(not ) false) \ { \ - error::mob::invoke(std::format(message __VA_OPT__(, ) __VA_ARGS__), data); \ + platform::mob::invoke(std::format(message __VA_OPT__(, ) __VA_ARGS__), data); \ } \ else \ { \ - error::mob::invoke(message, data); \ + platform::mob::invoke(message, data); \ } \ } while (false) @@ -428,3 +482,17 @@ #define GAL_PROMETHEUS_ERROR_DEBUG_ASSUME GAL_PROMETHEUS_ERROR_ASSUME #endif + +// ========================================================= +// MODULE: gal.prometheus.draw +// ========================================================= + + + +#if defined(DEBUG) or defined(_DEBUG) + +#if not defined(GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG) +#define GAL_PROMETHEUS_DRAW_CONTEXT_DEBUG +#endif + +#endif diff --git a/src/state_machine/state_machine.ixx b/src/state_machine/state_machine.ixx index bbb492ef..b4870fdc 100644 --- a/src/state_machine/state_machine.ixx +++ b/src/state_machine/state_machine.ixx @@ -3,24 +3,28 @@ // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#if GAL_PROMETHEUS_USE_MODULE -module; +#if not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED #include -export module gal.prometheus.state_machine; +export module gal.prometheus:state_machine; import std; -import gal.prometheus.functional; -import gal.prometheus.meta; -#else +import :functional; +import :meta; + +#endif not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED + +#if not GAL_PROMETHEUS_USE_MODULE + #pragma once #include #include #include + #include #include @@ -33,10 +37,8 @@ import gal.prometheus.meta; #define STATE_MACHINE_WORKAROUND_TEMPLATE_STATE_TYPE state #endif -namespace gal::prometheus::infrastructure +GAL_PROMETHEUS_COMPILER_MODULE_NAMESPACE_INTERNAL(sm) { - namespace state_machine_detail - { template class StateMachine; @@ -170,7 +172,7 @@ namespace gal::prometheus::infrastructure is_entry_point, from, to, - functional::type_list_type>, + functional::type_list_type, guard_type, action_type, sentry_entry_type, @@ -186,7 +188,7 @@ namespace gal::prometheus::infrastructure is_entry_point, from, to, - functional::type_list_type>, + functional::type_list_type, guard_type, action_type, sentry_entry_type, @@ -390,15 +392,15 @@ namespace gal::prometheus::infrastructure requires(event_type{}.template any()) [[nodiscard]] constexpr auto operator()(StateMachine& state_machine, const Event& event, Args&&... args) -> bool { - if (state_machine_detail::invoke(guard, event, std::forward(args)...)) + if (GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::invoke(guard, event, std::forward(args)...)) { - state_machine_detail::invoke(action, event, std::forward(args)...); + GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::invoke(action, event, std::forward(args)...); if constexpr (to != state_continue) { if constexpr (not std::is_same_v) // { - state_machine_detail::invoke(sentry_exit, event, std::forward(args)...); + GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::invoke(sentry_exit, event, std::forward(args)...); } state_machine.template transform(); @@ -411,7 +413,7 @@ namespace gal::prometheus::infrastructure if constexpr (not std::is_same_v) // { auto& to_sentry_entry = state_machine.template get_transition().sentry_entry; - state_machine_detail::invoke(to_sentry_entry, event, std::forward(args)...); + GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::invoke(to_sentry_entry, event, std::forward(args)...); } }; @@ -479,23 +481,23 @@ namespace gal::prometheus::infrastructure public: constexpr static auto transitions_list = list; - using transitions_list_type = functional::type_list_type; + using transitions_list_type = std::decay_t; constexpr static auto entry_point_list = list.template sub_list(); - using entry_point_list_type = functional::type_list_type; + using entry_point_list_type = std::decay_t; constexpr static auto state_list = list.template projection().unique(); - using state_list_type = functional::type_list_type; + using state_list_type = std::decay_t; template constexpr static auto state_to_transitions_list = list.template sub_list(); template - using state_to_transitions_list_type = functional::type_list_type>; + using state_to_transitions_list_type = std::decay_t)>; template constexpr static auto event_to_transitions_list = list.template sub_list(); template - using event_to_transitions_list_type = functional::type_list_type>; + using event_to_transitions_list_type = std::decay_t)>; }; template typename List, typename... Transitions> @@ -671,10 +673,18 @@ namespace gal::prometheus::infrastructure }; } - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN +#if GAL_PROMETHEUS_INTELLISENSE_WORKING +namespace GAL_PROMETHEUS_COMPILER_MODULE_NAMESPACE_PREFIX :: sm +#else +GAL_PROMETHEUS_COMPILER_MODULE_NAMESPACE_EXPORT(sm) +#endif +{ template [[nodiscard]] constexpr auto operator""_s() noexcept - -> state_machine_detail::transition(), state_machine_detail::state_continue> { return {}; } + -> GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::transition(), GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::state_continue> + { + return {}; + } // note: `any_state` will only change the state of the first entry_point when an event is processed. // transition_list{ @@ -687,33 +697,36 @@ namespace gal::prometheus::infrastructure // state_machine.is(s1, s2); // state_machine.process(e{}); // state_machine.is(s4, s2); // <== only the first entry_point changes state. - constexpr auto any_state = state_machine_detail::transition{}; + constexpr auto any_state = GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::transition< + false, + GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::state_any, + GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::state_continue + >{}; - template + template requires((Transitions::is_entry_point + ...) >= 1) #if defined(STATE_MACHINE_WORKAROUND_REQUIRED) - struct transition_list : state_machine_detail::transition_list_type + struct transition_list : GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::transition_list_type { - using state_machine_detail::transition_list_type::transition_list_type; + using GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::transition_list_type::transition_list_type; }; - template + template transition_list(Transitions && ...) -> transition_list; #else - using transition_list = state_machine_detail::transition_list_type; + using transition_list = GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::transition_list_type; #endif template - requires std::is_invocable_v and state_machine_detail::is_transition_list_v()())> - struct state_machine final : state_machine_detail::StateMachine()())> + requires std::is_invocable_v and GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::is_transition_list_v()())> + struct state_machine final : GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::StateMachine()())> { constexpr explicit(false) state_machine(Invocable function) noexcept // NOLINT(*-explicit-constructor) - : state_machine_detail::StateMachine()())>{function()} {} + : GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::StateMachine()())>{function()} {} }; template state_machine(Invocable) -> state_machine; - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END } #undef STATE_MACHINE_WORKAROUND_TEMPLATE_STATE_TYPE diff --git a/src/string/charconv.hpp b/src/string/charconv.hpp new file mode 100644 index 00000000..726e9f74 --- /dev/null +++ b/src/string/charconv.hpp @@ -0,0 +1,360 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include + +#include + +#include + +namespace gal::prometheus +{ + namespace meta + { + template + struct basic_fixed_string; + } + + namespace string + { + [[nodiscard]] constexpr auto is_upper(const char c) noexcept -> bool + { + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED { return c >= 'A' and c <= 'Z'; } + + return std::isupper(c); + } + + [[nodiscard]] constexpr auto is_upper(const std::basic_string_view string) noexcept -> bool + { + return std::ranges::all_of(string, static_cast bool>(is_upper)); + } + + [[nodiscard]] constexpr auto is_lower(const char c) noexcept -> bool + { + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED { return c >= 'a' and c <= 'z'; } + + return std::islower(c); + } + + [[nodiscard]] constexpr auto is_lower(const std::basic_string_view string) noexcept -> bool + { + return std::ranges::all_of(string, static_cast bool>(is_lower)); + } + + [[nodiscard]] constexpr auto is_alpha(const char c) noexcept -> bool + { + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED { return is_upper(c) or is_lower(c); } + + return std::isalpha(c); + } + + [[nodiscard]] constexpr auto is_alpha(const std::basic_string_view string) noexcept -> bool + { + return std::ranges::all_of(string, static_cast bool>(is_alpha)); + } + + [[nodiscard]] constexpr auto is_digit(const char c) noexcept -> bool + { + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED { return c >= '0' and c <= '9'; } + + return std::isdigit(c); + } + + [[nodiscard]] constexpr auto is_digit(const std::basic_string_view string) noexcept -> bool + { + return std::ranges::all_of(string, static_cast bool>(is_digit)); + } + + [[nodiscard]] constexpr auto is_alpha_digit(const char c) noexcept -> bool + { + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED { return is_alpha(c) or is_digit(c); } + + return std::isalnum(c); + } + + [[nodiscard]] constexpr auto is_alpha_digit(const std::basic_string_view string) noexcept -> bool + { + return std::ranges::all_of(string, static_cast bool>(is_alpha_digit)); + } + + [[nodiscard]] constexpr auto to_upper(const char c) noexcept -> char + { + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED { return static_cast(is_lower(c) ? c - 'a' + 'A' : c); } + + return static_cast(std::toupper(c)); + } + + constexpr auto to_upper(std::basic_string& string) noexcept -> void + { + std::ranges::for_each( + string, + [](char& c) noexcept -> void + { + c = to_upper(c); + }); + } + + [[nodiscard]] constexpr auto to_upper(const std::basic_string_view string) noexcept -> std::string + { + std::string result{}; + result.reserve(string.size()); + + std::ranges::for_each( + string, + [&result](const char c) noexcept -> void + { + result.push_back(to_upper(c)); + } + ); + + return result; + } + + [[nodiscard]] constexpr auto to_lower(const char c) noexcept -> char + { + GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED { return static_cast(is_upper(c) ? c - 'A' + 'a' : c); } + + return static_cast(std::tolower(c)); + } + + constexpr auto to_lower(std::basic_string& string) noexcept -> void + { + std::ranges::for_each( + string, + [](char& c) noexcept -> void + { + c = to_lower(c); + }); + } + + [[nodiscard]] constexpr auto to_lower(const std::basic_string_view string) noexcept -> std::string + { + std::string result{}; + result.reserve(string.size()); + + std::ranges::for_each( + string, + [&result](const char c) noexcept -> void + { + result.push_back(to_lower(c)); + }); + + return result; + } + + constexpr auto to_title(std::basic_string& string) noexcept -> void + { + std::ranges::for_each( + string, + [first = true](char& c) mutable noexcept -> void + { + if (first) + { + c = to_upper(c); + first = false; + } + else if (c == ' ') + { + first = true; + } + else + { + c = to_lower(c); + } + }); + } + + [[nodiscard]] constexpr auto to_title(const std::basic_string_view string) noexcept -> std::string + { + std::string result{}; + result.reserve(string.size()); + + std::ranges::for_each( + string, + [&result, first = true](const char c) mutable noexcept -> void + { + char this_char; + if (first) + { + this_char = to_upper(c); + first = false; + } + else if (c == ' ') + { + this_char = c; + first = true; + } + else + { + this_char = to_lower(c); + } + + result.push_back(this_char); + }); + + return result; + } + + template + [[nodiscard]] constexpr auto is_upper(const meta::basic_fixed_string string) noexcept -> bool + { + return std::ranges::all_of(string, static_cast bool>(is_upper)); + } + + template + [[nodiscard]] constexpr auto is_lower(const meta::basic_fixed_string string) noexcept -> bool + { + return std::ranges::all_of(string, static_cast bool>(is_lower)); + } + + template + [[nodiscard]] constexpr auto is_alpha(const meta::basic_fixed_string string) noexcept -> bool + { + return std::ranges::all_of(string, static_cast bool>(is_alpha)); + } + + + template + [[nodiscard]] constexpr auto is_digit(const meta::basic_fixed_string string) noexcept -> bool + { + return std::ranges::all_of(string, static_cast bool>(is_digit)); + } + + template + [[nodiscard]] constexpr auto is_alpha_digit(const meta::basic_fixed_string string) noexcept -> bool + { + return std::ranges::all_of(string, static_cast bool>(is_alpha_digit)); + } + + template + [[nodiscard]] constexpr auto to_upper(const meta::basic_fixed_string string) noexcept -> meta::basic_fixed_string + { + meta::basic_fixed_string result{string}; + + std::ranges::for_each( + result, + [](char& c) noexcept -> void + { + c = to_upper(c); + }); + + return result; + } + + template + [[nodiscard]] constexpr auto to_lower(const meta::basic_fixed_string string) noexcept -> meta::basic_fixed_string + { + meta::basic_fixed_string result{string}; + + std::ranges::for_each( + result, + [](char& c) noexcept -> void + { + c = to_lower(c); + }); + + return result; + } + + template + [[nodiscard]] constexpr auto to_title(const meta::basic_fixed_string string) noexcept -> meta::basic_fixed_string + { + meta::basic_fixed_string result{string}; + + std::ranges::for_each( + result, + [first = true](char& c) mutable noexcept -> void + { + if (first) + { + c = to_upper(c); + first = false; + } + else if (c == ' ') + { + first = true; + } + else + { + c = to_lower(c); + } + }); + + return result; + } + + template + requires(std::is_same_v or std::is_base_of_v) + [[nodiscard]] constexpr auto from_string( + const std::basic_string_view string, + int base = 10 + ) + noexcept(std::is_same_v) -> std::conditional_t, std::optional, T> + { + const auto begin = string.data(); + const auto end = string.data() + string.size(); + + T result; + + // fixme: + // C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.38.32919\include\utility(154): error C2752: "std::tuple_size<_Ty>": more than one partial specialization matches the template argument list + // with + // [ + // _Ty=const std::from_chars_result + // ] + // C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.38.32919\include\utility(604): note: maybe "std::tuple_size" + // C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.38.32919\include\utility(604): note: or "std::tuple_size" + // const auto [last, error_code] = std::from_chars(begin, end, result, base); + const auto from_chars_result = std::from_chars(begin, end, result, base); + const auto last = from_chars_result.ptr; + const auto error_code = from_chars_result.ec; + + if (error_code != std::errc{} or last != end) + { + if constexpr (std::is_same_v) + { + return std::nullopt; + } + else + { + platform::panic(std::format("Can not convert string [{}] to integer", string)); + } + } + + return result; + } + + template + requires(std::is_same_v or std::is_base_of_v) + [[nodiscard]] constexpr auto from_string(const std::basic_string_view string) + noexcept(std::is_same_v) -> std::conditional_t, std::optional, T> + { + const auto begin = string.data(); + const auto end = string.data() + string.size(); + + T result; + if ( + const auto [last, error_code] = std::from_chars(begin, end, result); + error_code != std::errc{} or last != end) + { + if constexpr (std::is_same_v) + { + return std::nullopt; + } + else + { + platform::panic(std::format("Can not convert string [{}] to floating point", string)); + } + } + + return result; + } + } +} diff --git a/src/string/charconv.ixx b/src/string/charconv.ixx deleted file mode 100644 index 153a74cf..00000000 --- a/src/string/charconv.ixx +++ /dev/null @@ -1,325 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.string:charconv; - -import std; -import gal.prometheus.error; - -#else -#pragma once - -#include -#include - -#include -#include - -#endif - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::string) -{ - [[nodiscard]] constexpr auto is_upper(const char c) noexcept -> bool - { - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED { return c >= 'A' and c <= 'Z'; } - - return std::isupper(c); - } - - [[nodiscard]] constexpr auto is_upper(const std::basic_string_view string) noexcept -> bool - { - return std::ranges::all_of(string, static_cast bool>(is_upper)); - } - - [[nodiscard]] constexpr auto is_lower(const char c) noexcept -> bool - { - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED { return c >= 'a' and c <= 'z'; } - - return std::islower(c); - } - - [[nodiscard]] constexpr auto is_lower(const std::basic_string_view string) noexcept -> bool - { - return std::ranges::all_of(string, static_cast bool>(is_lower)); - } - - [[nodiscard]] constexpr auto is_alpha(const char c) noexcept -> bool - { - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED { return is_upper(c) or is_lower(c); } - - return std::isalpha(c); - } - - [[nodiscard]] constexpr auto is_alpha(const std::basic_string_view string) noexcept -> bool - { - return std::ranges::all_of(string, static_cast bool>(is_alpha)); - } - - [[nodiscard]] constexpr auto is_digit(const char c) noexcept -> bool - { - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED { return c >= '0' and c <= '9'; } - - return std::isdigit(c); - } - - [[nodiscard]] constexpr auto is_digit(const std::basic_string_view string) noexcept -> bool - { - return std::ranges::all_of(string, static_cast bool>(is_digit)); - } - - [[nodiscard]] constexpr auto is_alpha_digit(const char c) noexcept -> bool - { - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED { return is_alpha(c) or is_digit(c); } - - return std::isalnum(c); - } - - [[nodiscard]] constexpr auto is_alpha_digit(const std::basic_string_view string) noexcept -> bool - { - return std::ranges::all_of(string, static_cast bool>(is_alpha_digit)); - } - - [[nodiscard]] constexpr auto to_upper(const char c) noexcept -> char - { - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED { return static_cast(is_lower(c) ? c - 'a' + 'A' : c); } - - return static_cast(std::toupper(c)); - } - - constexpr auto to_upper(std::basic_string& string) noexcept -> void - { - std::ranges::for_each( - string, - [](char& c) noexcept -> void { c = to_upper(c); }); - } - - [[nodiscard]] constexpr auto to_upper(const std::basic_string_view string) noexcept -> std::string - { - std::string result{}; - result.reserve(string.size()); - - std::ranges::for_each( - string, - [&result](const char c) noexcept -> void { result.push_back(to_upper(c)); } - ); - - return result; - } - - [[nodiscard]] constexpr auto to_lower(const char c) noexcept -> char - { - GAL_PROMETHEUS_SEMANTIC_IF_CONSTANT_EVALUATED { return static_cast(is_upper(c) ? c - 'A' + 'a' : c); } - - return static_cast(std::tolower(c)); - } - - constexpr auto to_lower(std::basic_string& string) noexcept -> void - { - std::ranges::for_each( - string, - [](char& c) noexcept -> void { c = to_lower(c); }); - } - - [[nodiscard]] constexpr auto to_lower(const std::basic_string_view string) noexcept -> std::string - { - std::string result{}; - result.reserve(string.size()); - - std::ranges::for_each( - string, - [&result](const char c) noexcept -> void { result.push_back(to_lower(c)); }); - - return result; - } - - constexpr auto to_title(std::basic_string& string) noexcept -> void - { - std::ranges::for_each( - string, - [first = true](char& c) mutable noexcept -> void - { - if (first) - { - c = to_upper(c); - first = false; - } - else if (c == ' ') { first = true; } - else { c = to_lower(c); } - }); - } - - [[nodiscard]] constexpr auto to_title(const std::basic_string_view string) noexcept -> std::string - { - std::string result{}; - result.reserve(string.size()); - - std::ranges::for_each( - string, - [&result, first = true](const char c) mutable noexcept -> void - { - char this_char; - if (first) - { - this_char = to_upper(c); - first = false; - } - else if (c == ' ') - { - this_char = c; - first = true; - } - else { this_char = to_lower(c); } - - result.push_back(this_char); - }); - - return result; - } - - // @see meta/string.ixx - // ------------------------------------------------------------ - // begin - // ------------------------------------------------------------ - - template - struct basic_fixed_string; - - template - [[nodiscard]] constexpr auto is_upper(const basic_fixed_string string) noexcept -> bool - { - return std::ranges::all_of(string, static_cast bool>(is_upper)); - } - - template - [[nodiscard]] constexpr auto is_lower(const basic_fixed_string string) noexcept -> bool - { - return std::ranges::all_of(string, static_cast bool>(is_lower)); - } - - template - [[nodiscard]] constexpr auto is_alpha(const basic_fixed_string string) noexcept -> bool - { - return std::ranges::all_of(string, static_cast bool>(is_alpha)); - } - - - template - [[nodiscard]] constexpr auto is_digit(const basic_fixed_string string) noexcept -> bool - { - return std::ranges::all_of(string, static_cast bool>(is_digit)); - } - - template - [[nodiscard]] constexpr auto is_alpha_digit(const basic_fixed_string string) noexcept -> bool - { - return std::ranges::all_of(string, static_cast bool>(is_alpha_digit)); - } - - template - [[nodiscard]] constexpr auto to_upper(const basic_fixed_string string) noexcept -> basic_fixed_string - { - basic_fixed_string result{string}; - - std::ranges::for_each( - result, - [](char& c) noexcept -> void { c = to_upper(c); }); - - return result; - } - - template - [[nodiscard]] constexpr auto to_lower(const basic_fixed_string string) noexcept -> basic_fixed_string - { - basic_fixed_string result{string}; - - std::ranges::for_each( - result, - [](char& c) noexcept -> void { c = to_lower(c); }); - - return result; - } - - template - [[nodiscard]] constexpr auto to_title(const basic_fixed_string string) noexcept -> basic_fixed_string - { - basic_fixed_string result{string}; - - std::ranges::for_each( - result, - [first = true](char& c) mutable noexcept -> void - { - if (first) - { - c = to_upper(c); - first = false; - } - else if (c == ' ') { first = true; } - else { c = to_lower(c); } - }); - - return result; - } - - // ------------------------------------------------------------ - // end - // ------------------------------------------------------------ - - template - requires(std::is_same_v or std::is_base_of_v) - [[nodiscard]] constexpr auto from_string(const std::basic_string_view string, int base = 10) - noexcept(std::is_same_v) -> std::conditional_t, std::optional, T> - { - const auto begin = string.data(); - const auto end = string.data() + string.size(); - - T result; - - // fixme: - // C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.38.32919\include\utility(154): error C2752: "std::tuple_size<_Ty>": more than one partial specialization matches the template argument list - // with - // [ - // _Ty=const std::from_chars_result - // ] - // C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.38.32919\include\utility(604): note: maybe "std::tuple_size" - // C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.38.32919\include\utility(604): note: or "std::tuple_size" - // const auto [last, error_code] = std::from_chars(begin, end, result, base); - const auto from_chars_result = std::from_chars(begin, end, result, base); - const auto last = from_chars_result.ptr; - const auto error_code = from_chars_result.ec; - - if (error_code != std::errc{} or last != end) - { - if constexpr (std::is_same_v) { return std::nullopt; } - else { error::panic(std::format("Can not convert string [{}] to integer", string)); } - } - - return result; - } - - template - requires(std::is_same_v or std::is_base_of_v) - [[nodiscard]] constexpr auto from_string(const std::basic_string_view string) - noexcept(std::is_same_v) -> std::conditional_t, std::optional, T> - { - const auto begin = string.data(); - const auto end = string.data() + string.size(); - - T result; - if ( - const auto [last, error_code] = std::from_chars(begin, end, result); - error_code != std::errc{} or last != end) - { - if constexpr (std::is_same_v) { return std::nullopt; } - else { error::panic(std::format("Can not convert string [{}] to floating point", string)); } - } - - return result; - } -} diff --git a/src/string/string.hpp b/src/string/string.hpp new file mode 100644 index 00000000..0fb480e4 --- /dev/null +++ b/src/string/string.hpp @@ -0,0 +1,9 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include diff --git a/src/string/string.ixx b/src/string/string.ixx deleted file mode 100644 index 708b4429..00000000 --- a/src/string/string.ixx +++ /dev/null @@ -1,18 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -export module gal.prometheus.string; - -export import :charconv; -export import :string_pool; - -#else -#pragma once - -#include -#include - -#endif diff --git a/src/string/string_pool.hpp b/src/string/string_pool.hpp new file mode 100644 index 00000000..03b07db4 --- /dev/null +++ b/src/string/string_pool.hpp @@ -0,0 +1,310 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE + +namespace gal::prometheus::string +{ + namespace string_pool_detail + { + template> + class StringBlock + { + public: + constexpr static bool is_null_terminate = IsNullTerminate; + + using view_type = std::basic_string_view; + using value_type = typename view_type::value_type; + using size_type = typename view_type::size_type; + + constexpr static CharType invalid_char{'\0'}; + + private: + std::unique_ptr memory_; + size_type capacity_; + size_type size_; + + public: + constexpr explicit StringBlock(const size_type capacity) noexcept + : memory_{std::make_unique_for_overwrite(capacity)}, + capacity_{capacity}, + size_{0} {} + + constexpr StringBlock(StringBlock&&) noexcept = default; + constexpr auto operator=(StringBlock&&) noexcept -> StringBlock& = default; + + // Allow blocks to copy constructs, and furthermore allow pools to copy constructs + constexpr StringBlock(const StringBlock& other) noexcept + : memory_{std::make_unique_for_overwrite(other.capacity_)}, + capacity_{other.capacity_}, + size_{other.size_} + { + std::ranges::uninitialized_copy_n( + other.memory_.get(), + other.size_, + memory_.get(), + memory_.get() + other.size_ + ); + } + + // Allow blocks to copy constructs, and furthermore allow pools to copy constructs + constexpr auto operator=(const StringBlock& other) noexcept -> StringBlock& + { + memory_ = std::make_unique_for_overwrite(other.capacity_); + capacity_ = other.capacity_; + size_ = other.size_; + + std::ranges::uninitialized_copy_n( + other.memory_.get(), + other.size_, + memory_.get(), + memory_.get() + other.size_ + ); + + return *this; + } + + constexpr ~StringBlock() noexcept = default; + + [[nodiscard]] constexpr static auto length_of(const view_type string) noexcept -> size_type + { + if constexpr (is_null_terminate) + { + return string.length() + 1; + } + else + { + return string.length(); + } + } + + [[nodiscard]] constexpr auto append(const view_type string) noexcept -> view_type + { + if (not this->storable(string)) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(this->storable(string), "There are not enough space for this string."); + return &invalid_char; + } + + const auto dest = memory_.get() + size_; + std::ranges::copy(string, dest); + + if constexpr (is_null_terminate) + { + dest[string.length()] = 0; + } + + size_ += StringBlock::length_of(string); + return {dest, string.length()}; + } + + [[nodiscard]] constexpr auto storable(const view_type str) const noexcept -> bool + { + return available_space() >= StringBlock::length_of(str); + } + + [[nodiscard]] constexpr auto storable(const size_type size) const noexcept -> bool + { + return available_space() >= size; + } + + [[nodiscard]] constexpr auto available_space() const noexcept -> size_type + { + return capacity_ - size_; + } + + [[nodiscard]] constexpr auto more_available_space_than(const StringBlock& other) const noexcept -> bool + { + // preserving equivalent elements original order + return available_space() >= other.available_space(); + } + + friend constexpr auto swap(StringBlock& lhs, StringBlock& rhs) noexcept -> void + { + using std::swap; + swap(lhs.memory_, rhs.memory_); + swap(lhs.capacity_, rhs.capacity_); + swap(lhs.size_, rhs.size_); + } + }; + } + + + template> + class StringPool + { + using block_type = string_pool_detail::StringBlock; + using pool_type = std::vector; + + public: + using view_type = typename block_type::view_type; + using value_type = typename block_type::value_type; + using size_type = typename block_type::size_type; + + constexpr static size_type default_block_initial_size = 8192; + + private: + pool_type pool_; + size_type block_initial_size_; + + using block_iterator = typename pool_type::iterator; + + [[nodiscard]] constexpr auto find_first_possible_storable_block(const size_type length) noexcept -> block_iterator + { + if (pool_.size() > 2 && not std::ranges::prev(pool_.end(), 2)->storable(length)) + { + return std::ranges::prev(pool_.end()); + } + return pool_.begin(); + } + + [[nodiscard]] constexpr auto find_storable_block(const size_type length) noexcept -> block_iterator + { + return std::ranges::partition_point( + this->find_first_possible_storable_block(length), + pool_.end(), + [length](const auto& block) + { + return not block.storable(length); + } + ); + } + + [[nodiscard]] constexpr auto create_storable_block(const size_type length) -> block_iterator + { + pool_.emplace_back(std::ranges::max(block_initial_size_, length + IsNullTerminate)); + return std::ranges::prev(pool_.end()); + } + + [[nodiscard]] constexpr auto find_or_create_block(const size_type length) -> block_iterator + { + if (const auto block = this->find_storable_block(length); + block != pool_.end()) + { + return block; + } + return this->create_storable_block(length); + } + + constexpr auto shake_it(block_iterator block) -> void + { + if ( + block == pool_.begin() || + block->more_available_space_than(*std::ranges::prev(block)) + ) + { + return; + } + + if (const auto it = + std::ranges::upper_bound( + pool_.begin(), + block, + block->available_space(), + std::ranges::less{}, + [](const auto& b) + { + return b.available_space(); + }); + it != block) + { + std::ranges::rotate(it, block, std::ranges::next(block)); + } + } + + [[nodiscard]] constexpr auto append_string_into_block(const view_type string, block_iterator block) -> view_type + { + const auto view = block->append(string); + + this->shake_it(block); + return view; + } + + public: + constexpr explicit StringPool(size_type block_initial_size = default_block_initial_size) noexcept(std::is_nothrow_default_constructible_v) + : block_initial_size_(block_initial_size) {} + + template + requires (std::same_as> and ...) + constexpr explicit StringPool(Pools&&... pools) + { + this->join(std::forward(pools)...); + } + + template + requires (std::same_as> and ...) + constexpr auto join(Pools&&... pools) -> void + { + pool_.reserve(pool_.size() + (pools.pool_.size() + ...)); + + const auto f = [this](Pool&& pool) noexcept -> void + { + // silence warning + auto&& p = std::forward(pool); + + const auto size = pool_.size(); + if constexpr (std::is_rvalue_reference_v) + { + pool_.insert(pool_.end(), std::make_move_iterator(p.pool_.begin()), std::make_move_iterator(p.pool_.end())); + p.pool_.clear(); + } + else + { + #if __has_cpp_attribute(cpp_lib_containers_ranges) and cpp_lib_containers_ranges >= 202202L + pool_.insert_range(pool_.end(), p.pool_); + #else + pool_.insert(pool_.end(), p.pool_.begin(), p.pool_.end()); + #endif + } + + std::ranges::inplace_merge( + pool_, + std::ranges::next(pool_.begin(), size), + [](const auto& a, const auto& b) + { + return not a.more_available_space_than(b); + }); + }; + (f.operator()(std::forward(pools)), ...); + } + + /** + * @brief Add a string to the pool, and then you can freely use the added string. + */ + [[nodiscard]] constexpr auto add(const view_type string) -> view_type + { + return this->append_string_into_block(string, this->find_or_create_block(string.size())); + } + + [[nodiscard]] constexpr auto size() const noexcept -> size_type + { + return pool_.size(); + } + + [[nodiscard]] constexpr auto block_initial_size() const noexcept -> size_type + { + return block_initial_size_; + } + + /** + * @note Only affect the block created after modification + */ + constexpr auto reset_block_initial_size(size_type capacity) noexcept -> void + { + block_initial_size_ = capacity; + } + }; +} diff --git a/src/string/string_pool.ixx b/src/string/string_pool.ixx deleted file mode 100644 index d00b6435..00000000 --- a/src/string/string_pool.ixx +++ /dev/null @@ -1,386 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.string:string_pool; - -import std; -import gal.prometheus.error; - -#else -#pragma once - -#include -#include -#include -#include - -#include -#include - -#endif - -GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_NAMESPACE(gal::prometheus::string) -{ - template> - class StringPool - { - class StringBlock - { - public: - constexpr static bool is_null_terminate = IsNullTerminate; - - using view_type = std::basic_string_view; - using value_type = typename view_type::value_type; - using size_type = typename view_type::size_type; - - constexpr static CharType invalid_char{'\0'}; - - private: - std::unique_ptr memory_; - size_type capacity_; - size_type size_; - - public: - constexpr explicit StringBlock(const size_type capacity) noexcept - : memory_{std::make_unique(capacity)}, - capacity_{capacity}, - size_{0} {} - - [[nodiscard]] constexpr static auto length_of(const view_type str) noexcept -> size_type - { - if constexpr (is_null_terminate) { return str.length() + 1; } - else { return str.length(); } - } - - [[nodiscard]] constexpr auto append(const view_type str) noexcept -> view_type - { - if (not this->storable(str)) - { - GAL_PROMETHEUS_DEBUG_ASSUME(this->storable(str), "There are not enough space for this string."); - return &invalid_char; - } - - const auto dest = memory_.get() + size_; - std::ranges::copy(str, dest); - - if constexpr (is_null_terminate) { dest[str.length()] = 0; } - - size_ += this->length_of(str); - return {dest, str.length()}; - } - - [[nodiscard]] constexpr auto borrow_raw(const size_type size) noexcept -> value_type* - { - if (not this->storable(size)) - { - GAL_PROMETHEUS_DEBUG_ASSUME(this->storable(size), "There are not enough space for this string."); - return nullptr; - } - - const auto dest = memory_.get() + size_; - - if constexpr (is_null_terminate) { dest[size] = 0; } - size_ += size; - - return dest; - } - - constexpr auto return_raw(const value_type* raw, const size_type size) noexcept -> void - { - auto memory_data = memory_.get(); - - GAL_PROMETHEUS_DEBUG_ASSUME(memory_data >= (raw - (size_ - size)), "The given memory does not belong to this block"); - - if (memory_data > (raw - (size_ - size))) - { - // In this case, new string is inserted after the memory is borrowed, and the newly added string needs to be moved to the front - auto move_dest = memory_data + (raw - memory_data); // raw; - GAL_PROMETHEUS_DEBUG_ASSUME(move_dest == raw); - auto move_source = raw + size; - auto move_size = size_ - size - (raw - memory_data); - - std::ranges::copy_n(move_source, move_size, move_dest); - } - - size_ -= size; - - if constexpr (is_null_terminate) { memory_.get()[size_] = 0; } - } - - [[nodiscard]] constexpr auto storable(const view_type str) const noexcept -> bool { return available_space() >= this->length_of(str); } - - [[nodiscard]] constexpr auto storable(const size_type size) const noexcept -> bool { return available_space() >= size; } - - [[nodiscard]] constexpr auto available_space() const noexcept -> size_type { return capacity_ - size_; } - - [[nodiscard]] constexpr auto more_available_space_than(const StringBlock& other) const noexcept -> bool // - { - return available_space() > other.available_space(); - } - - friend constexpr auto swap(StringBlock& lhs, StringBlock& rhs) noexcept -> void - { - using std::swap; - swap(lhs.memory_, rhs.memory_); - swap(lhs.capacity_, rhs.capacity_); - swap(lhs.size_, rhs.size_); - } - }; - - using block_type = StringBlock; - using pool_type = std::vector; - - public: - using view_type = typename block_type::view_type; - using value_type = typename block_type::value_type; - using size_type = typename block_type::size_type; - - constexpr static size_type default_capacity = 8196; - - private: - pool_type pool_; - size_type capacity_; - - using block_iterator = typename pool_type::iterator; - - public: - // fixme: The borrower has many optimizations, - // such as separating the borrow pool from the actual pool, - // inserting strings into the borrower pool first, - // and then inserting it back into the actual pool when the borrower is destructed. - // The current implementation is to record where all strings are inserted, and then delete them one by one, which involves multiple moves (to move the following strings forward) - class BlockBorrower - { - std::reference_wrapper pool_; - // pair -> block memory view <=> which block - std::vector> borrowed_blocks_; - bool need_return_ = true; - - public: - [[nodiscard]] constexpr auto need_return() const noexcept -> bool { return need_return_; } - - constexpr auto break_promise() noexcept -> void { need_return_ = false; } - - constexpr explicit BlockBorrower(StringPool& pool) noexcept - : pool_{pool} {} - - constexpr BlockBorrower(const BlockBorrower&) = delete; - constexpr BlockBorrower(BlockBorrower&&) noexcept = default; - constexpr auto operator=(const BlockBorrower&) -> BlockBorrower& = delete; - constexpr auto operator=(BlockBorrower&&) -> BlockBorrower& = default; - - constexpr ~BlockBorrower() noexcept - { - if (need_return_) - { - std::ranges::for_each( - // back to front, because the last inserted string is saved at the end - borrowed_blocks_ | std::views::reverse, - [this](auto& pair) mutable - { - auto& pool = pool_.get(); - - pool.return_raw_memory(pair.first, pair.second); - // fixme: maybe a lock is needed here? - // erase is not performed because the target may still have memory in use. - // pool.erase(pair.second); - }); - } - } - - /** - * @brief Add a string to the pool, and then you can freely use the added string. - */ - constexpr auto append(const view_type str) -> view_type - { - // see also: StringPool::append - - // not only to insert the string, but also to save the inserted position - auto& pool = pool_.get(); - - auto pos = pool.find_or_create_block(str); - return borrowed_blocks_.emplace_back(pool.append_str_into_block(str, pos), pos).first; - } - - /** - * @brief Borrow a block of memory to the pool, users can directly write strings in this memory area without worrying about its invalidation. - */ - [[nodiscard]] constexpr auto borrow_raw(const size_type size) -> value_type* - { - // see also: StringPool::borrow_raw - - // not only to insert the string, but also to save the inserted position - auto& pool = pool_.get(); - - auto pos = pool.find_or_create_block(size); - return borrowed_blocks_.emplace_back({pool.borrow_raw_memory(size, pos), size}, pos).first.data(); - } - }; - - private: - [[nodiscard]] constexpr auto append_str_into_block(const view_type str, block_iterator pos) -> view_type - { - const auto ret = pos->append(str); - - this->shake_it(pos); - return ret; - } - - [[nodiscard]] constexpr auto borrow_raw_memory(const size_type size, block_iterator pos) -> value_type* - { - auto raw = pos->borrow_raw(size); - - this->shake_it(pos); - return raw; - } - - constexpr auto return_raw_memory(const value_type* raw, const size_type size, block_iterator pos) -> void - { - pos->return_raw(raw, size); - - this->shake_it(pos); - } - - constexpr auto return_raw_memory(view_type view, block_iterator pos) -> void { this->return_raw_memory(view.data(), view.size(), pos); } - - [[nodiscard]] constexpr auto find_or_create_block(const size_type size) -> block_iterator - { - if (const auto block = this->find_storable_block(size); block != pool_.end()) { return block; } - return this->create_storable_block(size); - } - - [[nodiscard]] constexpr auto find_or_create_block(const view_type str) -> block_iterator { return this->find_or_create_block(str.size()); } - - [[nodiscard]] constexpr auto find_first_possible_storable_block(const size_type size) noexcept -> block_iterator - { - if (pool_.size() > 2 && not std::ranges::prev(pool_.end(), 2)->storable(size)) { return std::ranges::prev(pool_.end()); } - return pool_.begin(); - } - - [[nodiscard]] constexpr auto find_first_possible_storable_block(const view_type str) noexcept -> block_iterator - { - return this->find_first_possible_storable_block(str.size()); - } - - [[nodiscard]] constexpr auto find_storable_block(const size_type size) noexcept -> block_iterator - { - return std::ranges::lower_bound( - this->find_first_possible_storable_block(size), - pool_.end(), - true, - [](bool b, bool) { return b; }, - [size](const auto& block) { return not block.storable(size); }); - } - - [[nodiscard]] constexpr auto find_storable_block(const view_type str) noexcept -> block_iterator - { - return this->find_storable_block(str.size()); - } - - [[nodiscard]] constexpr auto create_storable_block(const size_type size) -> block_iterator - { - pool_.emplace_back(std::ranges::max(capacity_, size + IsNullTerminate)); - return std::ranges::prev(pool_.end()); - } - - [[nodiscard]] constexpr auto create_storable_block(const view_type str) -> block_iterator { return this->create_storable_block(str.size()); } - - constexpr auto shake_it(block_iterator block) -> void - { - if ( - block == pool_.begin() || - block->more_available_space_than(*std::ranges::prev(block))) { return; } - - if (const auto it = - std::ranges::upper_bound( - pool_.begin(), - block, - block->available_space(), - std::ranges::less{}, - [](const auto& b) { return b.available_space(); }); - it != block) { std::ranges::rotate(it, block, std::ranges::next(block)); } - } - - public: - constexpr explicit StringPool(size_type capacity = default_capacity) noexcept(std::is_nothrow_default_constructible_v) - : capacity_(capacity) {} - - template... Pools> - constexpr explicit StringPool(Pools&&... pools) { this->append(std::forward(pools)...); } - - template... Pools> - constexpr auto takeover(Pools&&... pools) -> void - { - pool_.reserve(pool_.size() + (pools.pool_.size() + ...)); - - block_iterator iterator; - ( // - ( // - ( - iterator = pool_.insert(pool_.end(), std::make_move_iterator(pools.pool_.begin()), std::make_move_iterator(pools.pool_.end())) - ), - pools.pool_.clear(), - std::ranges::inplace_merge( - pool_.begin(), - iterator, - pool_.end(), - [](const auto& a, const auto& b) { return not a.more_available_space_than(b); }) - ), - ...); - } - - template... Pools> - constexpr auto copy(const Pools&... pools) -> void - { - pool_.reserve(pool_.size() + (pools.pool_.size() + ...)); - - block_iterator iterator; - ( // - ( // - ( - iterator = pool_.insert(pool_.end(), pools.pool_.begin(), pools.pool_.end()) - ), - std::ranges::inplace_merge( - pool_.begin(), - iterator, - pool_.end(), - [](const auto& a, const auto& b) { return not a.more_available_space_than(b); }) - ), - ...); - } - - /** - * @brief Add a string to the pool, and then you can freely use the added string. - */ - constexpr auto append(const view_type str) -> view_type { return this->append_str_into_block(str, this->find_or_create_block(str)); } - - /** - * @brief Borrow a block of memory to the pool, users can directly write strings in this memory area without worrying about its invalidation. - */ - [[nodiscard]] constexpr auto borrow_raw(const size_type size = default_capacity) -> value_type* - { - return this->borrow_raw_memory(size, this->find_or_create_block(size)); - } - - /** - * @brief User needs to temporarily use some memory area to store the string and return it later - */ - [[nodiscard]] constexpr auto borrow_block() noexcept -> BlockBorrower { return BlockBorrower{*this}; } - - [[nodiscard]] constexpr auto size() const noexcept -> size_type { return pool_.size(); } - - [[nodiscard]] constexpr auto capacity() const noexcept -> size_type { return capacity_; } - - /** - * @note Only affect the block created after modification - */ - constexpr auto resize(size_type capacity) noexcept -> void { capacity_ = capacity; } - }; -} diff --git a/src/unit_test/def.hpp b/src/unit_test/def.hpp new file mode 100644 index 00000000..dcbad6e9 --- /dev/null +++ b/src/unit_test/def.hpp @@ -0,0 +1,267 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include +#include + +#include + +// fixme +#if __has_include() +#include +#else +#include +#endif + +namespace gal::prometheus::unit_test +{ + // ===================================== + // TIME + // ===================================== + + using clock_type = std::chrono::high_resolution_clock; + using time_point_type = clock_type::time_point; + using time_difference_type = std::chrono::milliseconds; + + struct time_range_type + { + time_point_type start; + time_point_type end; + + time_range_type() noexcept + : start{clock_type::now()} {} + + [[nodiscard]] auto count() noexcept -> time_point_type::rep + { + end = clock_type::now(); + return std::chrono::duration_cast(end - start).count(); + } + }; + + // ===================================== + // FILTER + // ===================================== + + using suite_name_type = std::string; + using suite_name_view_type = std::string_view; + using test_name_type = std::string; + using test_name_view_type = std::string_view; + using test_category_type = std::string; + using test_category_view_type = std::string_view; + using test_categories_type = std::vector; + using test_categories_view_type = std::reference_wrapper; + + // nested suite is not allowed + struct suite_node_type + { + suite_name_view_type name; + }; + + // ROOT_TEST->NESTED_TEST->NESTED_TEST + struct test_node_type + { + // parent == nullptr ==> ROOT + std::unique_ptr parent; + + test_name_view_type name; + test_categories_view_type categories; + }; + + using suite_filter_type = std::function; + using test_filter_type = std::function; + + // ===================================== + // RESULT + // ===================================== + + using report_string_type = std::string; + + struct test_result_type; + using test_results_type = std::vector; + + struct suite_result_type + { + suite_name_type name; + test_results_type results; + + report_string_type report_string; + }; + + using suite_results_type = std::vector; + + struct test_result_type + { + enum class Status : std::uint8_t + { + // The current test has not been executed yet (internal use only) + PENDING, + + // All assertions passed + PASSED, + // At least one assertion failed + FAILED, + // No assertions found in test or + SKIPPED_NO_ASSERTION, + // The test was filtered + SKIPPED_FILTERED, + // The test execution was interrupted (at least one fatal assertion failed) + INTERRUPTED, + // The number of errors has reached the specified threshold config_type::abort_after_n_failures, + // all suite/test executions are terminated and the last test is marked TERMINATED + TERMINATED, + }; + + test_name_type name; + + test_result_type* parent; + test_results_type children; + + std::size_t total_assertions_passed; + std::size_t total_assertions_failed; + + time_range_type time; + Status status; + }; + + // ===================================== + // CONFIG + // ===================================== + + struct config_type + { + struct color_type + { + std::string_view none = "\033[0m"; + + std::string_view failure = "\033[31m\033[7m"; + std::string_view pass = "\033[32m\033[7m"; + std::string_view skip = "\033[33m\033[7m"; + std::string_view fatal = "\033[35m\033[7m"; + + std::string_view suite = "\033[34m\033[7m"; + std::string_view test = "\033[36m\033[7m"; + std::string_view expression = "\033[38;5;207m\033[7m"; + std::string_view message = "\033[38;5;27m\033[7m"; + }; + + enum class ReportLevel : std::uint16_t + { + // for functional::enumeration + PROMETHEUS_MAGIC_ENUM_FLAG = 0b0000'0000'0000'0000, + + // SUITE & TEST NAME + SUITE_NAME = 0b0000'0000'0000'0001, + TEST_NAME = 0b0000'0000'0000'0010 | SUITE_NAME, + + // ASSERTION + ASSERTION_FATAL = 0b0000'0000'0001'0000 | TEST_NAME, + ASSERTION_FAILURE = 0b0000'0000'0010'0000 | ASSERTION_FATAL, + ASSERTION_SKIP = 0b0000'0000'0100'0000 | ASSERTION_FAILURE, + ASSERTION_PASS = 0b0000'0000'1000'0000 | ASSERTION_SKIP, + + ASSERTION_ERROR_ONLY = ASSERTION_FAILURE, + ASSERTION_NOT_PASS = ASSERTION_SKIP, + ASSERTION_ALL = ASSERTION_PASS, + + DEFAULT = ASSERTION_NOT_PASS, + NO_ASSERTION = TEST_NAME, + ALL = ASSERTION_ALL, + NONE = 0b1000'0000'0000'0000, + }; + + enum class BreakPointLevel : std::uint8_t + { + // for functional::enumeration + PROMETHEUS_MAGIC_ENUM_FLAG = 0b0000'0000, + + FATAL = 0b0000'0001, + FAILURE = 0b0000'0010, + + NONE = 0b0001'0000, + }; + + // ===================================== + // OUTPUT + // ===================================== + + color_type color = {}; + + // Dynamic width, the space before each output is based on the nesting depth and tab width + // std::format("{:{}}-xxx", "", tab_width * depth) + std::size_t tab_width = 4; + // The prefix of each output is not recommended to be longer than tab_width * depth + // prefix = "Prefix:" + // [tab_width * depth == 10] ==> [Prefix: OUTPUT] + // [tab_width * depth == 20] ==> [Prefix: OUTPUT] + // [tab_width * depth == 5] ==> [Prefix:OUTPUT] + std::string_view prefix = "-"; + + std::function out = [](const suite_results_type& results) noexcept -> void + { + std::ranges::for_each( + results, + [](const suite_result_type& result) noexcept -> void + { + #if __has_include() + std::println(stdout, "{}", result.report_string); + #else + std::fputs(result.report_string.c_str(), stdout); + std::putc('\n', stdout); + #endif + } + ); + }; + + ReportLevel report_level = ReportLevel::DEFAULT; + + // ===================================== + // RUN + // ===================================== + + bool dry_run = false; + + BreakPointLevel break_point_level = BreakPointLevel::NONE; + + std::size_t abort_after_n_failures = std::numeric_limits::max(); + + // ===================================== + // FILTER + // ===================================== + + std::function filter_suite = [](const suite_node_type& node) noexcept -> bool + { + std::ignore = node; + return true; + }; + + std::function filter_test = [](const test_node_type& node) noexcept -> bool + { + return not std::ranges::contains(node.categories.get(), "skip"); + }; + }; + + // ===================================== + // EXPRESSION + // ===================================== + + template + struct is_expression + : std::bool_constant< + // implicit + std::is_convertible_v or + // explicit + std::is_constructible_v + > {}; + + template + constexpr auto is_expression_v = is_expression::value; + template + concept expression_t = is_expression_v; +} diff --git a/src/unit_test/dispatcher.hpp b/src/unit_test/dispatcher.hpp new file mode 100644 index 00000000..d5188be0 --- /dev/null +++ b/src/unit_test/dispatcher.hpp @@ -0,0 +1,2952 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace gal::prometheus::unit_test::dispatcher +{ + template + struct dispatched_expression + { + using expression_type = Lhs; + using dispatcher_type = Dispatcher; + + expression_type expression; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + + // explicit + [[nodiscard]] constexpr explicit operator bool() const noexcept // + { + return static_cast(expression); + } + }; + + template + struct is_dispatched_expression : std::false_type {}; + + template + struct is_dispatched_expression> : std::true_type {}; + + template + constexpr auto is_dispatched_expression_v = is_dispatched_expression::value; + template + concept dispatched_expression_t = is_dispatched_expression_v; + + template + struct is_type : std::bool_constant, std::remove_cvref_t>> {}; + + template + constexpr auto is_type_v = is_type::value; + + template + concept type_t = is_type_v; + + template + struct is_type_or_dispatched_type : std::bool_constant< + // In most cases the following line will already fulfill the requirement + is_type_v or + // The next two lines allow us to support more complex expressions, such as `(xxx == xxx) == "same!"_b)` + // implicit + std::is_convertible_v, std::remove_cvref_t> or + // explicit + std::is_constructible_v, std::remove_cvref_t> + > {}; + + template + struct is_type_or_dispatched_type, RequiredType> : is_type_or_dispatched_type {}; + + template + constexpr auto is_type_or_dispatched_type_v = is_type_or_dispatched_type::value; + + template + concept type_or_dispatched_type_t = is_type_or_dispatched_type_v; + + template typename Constrain> + struct is_constrain_satisfied_type_or_dispatched_type : std::bool_constant>::value> {}; + + template typename Constrain, typename Dispatcher> + struct is_constrain_satisfied_type_or_dispatched_type, Constrain> : + is_constrain_satisfied_type_or_dispatched_type::expression_type, Constrain> {}; + + template typename Constrain> + constexpr auto is_constrain_satisfied_type_or_dispatched_type_v = is_constrain_satisfied_type_or_dispatched_type::value; + + template typename Constrain> + concept constrain_satisfied_type_or_dispatched_type_t = is_constrain_satisfied_type_or_dispatched_type_v; + + template typename Constrain> + struct constrain_against_wrapper + { + template + struct rebind + { + constexpr static auto value = not Constrain::value; + }; + }; + + template typename Constrain> + constexpr auto is_constrain_against_type_or_dispatched_type_v = is_constrain_satisfied_type_or_dispatched_type_v< + T, + constrain_against_wrapper::template rebind + >; + + template typename Constrain> + concept constrain_against_type_or_dispatched_type_t = is_constrain_against_type_or_dispatched_type_v; + + struct type_or_dispatched_type + { + template + [[nodiscard]] constexpr static auto get(T&& v) noexcept -> decltype(auto) // + { + if constexpr (is_dispatched_expression_v>) // + { + return std::forward(v).expression; + } + else // + { + return std::forward(v); + } + } + }; + + template + struct lazy_dispatcher_type; + + template + struct lazy_dispatcher_type + { + using type = typename L::dispatcher_type; + }; + + template + struct lazy_dispatcher_type + { + using type = typename R::dispatcher_type; + }; + + template + using lazy_dispatcher_type_t = typename lazy_dispatcher_type::type; + + template + struct lazy_expression_type; + + template + struct lazy_expression_type + { + using type = typename T::expression_type; + }; + + template + struct lazy_expression_type + { + using type = T; + }; + + template + using lazy_expression_type_t = typename lazy_expression_type::type; + + // ============================================ + // operator== + // ============================================ + + // OperandValue + + // floating_point == value{...} + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R> + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator==(const L& lhs, const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::APPROX, + left_expression_type, + typename right_expression_type::value_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + type_or_dispatched_type::get(rhs).value(), + std::numeric_limits::epsilon() + } + }; + } + + // value{...} == floating_point + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R> + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator==(const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs == lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::APPROX, + typename left_expression_type::value_type, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs).value(), + type_or_dispatched_type::get(rhs), + std::numeric_limits::epsilon() + } + }; + } + + // not(floating_point) == value{...} + template< + constrain_against_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R> + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator==(const L& lhs, const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::EQUAL, + left_expression_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + type_or_dispatched_type::get(rhs).value() + } + }; + } + + // value{...} == not(floating_point) + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_against_type_or_dispatched_type_t R> + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator==(const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs == lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::EQUAL, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs).value(), + type_or_dispatched_type::get(rhs) + } + }; + } + + // OperandLiteralXxx + + // character == "xxx"_c + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R> + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator==(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::EQUAL, + left_expression_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + right_expression_type::value + } + }; + } + + // "xxx"_c == character + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R> + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator==([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs == lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::EQUAL, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + left_expression_type::value, + type_or_dispatched_type::get(rhs) + } + }; + } + + // integral == "xxx"_x + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R> + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator==(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::EQUAL, + left_expression_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + right_expression_type::value + } + }; + } + + // "xxx"_x == integral + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R> + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator==([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs == lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::EQUAL, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + left_expression_type::value, + type_or_dispatched_type::get(rhs) + } + }; + } + + // floating_point == "xxx"_x + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R> + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator==(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::APPROX, + left_expression_type, + typename right_expression_type::value_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + right_expression_type::value, + right_expression_type::epsilon + } + }; + } + + // "xxx"_x == floating_point + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R> + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator==([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs == lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::APPROX, + typename left_expression_type::value_type, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + left_expression_type::value, + type_or_dispatched_type::get(rhs), + left_expression_type::epsilon + } + }; + } + + // ? == "xxx"_auto + template< + typename L, + constrain_satisfied_type_or_dispatched_type_t R> + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + // rebind-able type + and requires + { + typename lazy_expression_type_t, R>::template rebind< + lazy_expression_type_t, L> + >; + } + [[nodiscard]] constexpr auto operator==(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + // forward + // lhs == dispatched_expression{} + return lhs == dispatched_expression, dispatcher_type>{}; + } + + // "xxx"_auto == ? + template< + constrain_satisfied_type_or_dispatched_type_t L, + typename R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + // rebind-able type + and requires + { + typename lazy_expression_type_t, L>::template rebind< + lazy_expression_type_t, R> + >; + } + [[nodiscard]] constexpr auto operator==([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs == lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + // forward + // dispatched_expression{} == rhs + return dispatched_expression, dispatcher_type>{} == rhs; + } + + // OperandIdentityBoolean + + // bool == operands::OperandIdentityBoolean::value_type{...} + template< + type_or_dispatched_type_t L, + // not allowed to be a candidate for an expression such as `xxx == "xxx"`, must be `xxx == "xxx"_b` + type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator==(const L& lhs, const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + return dispatched_expression< + operands::OperandIdentityBoolean, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(rhs), + // (xxx == xxx) == "xxx"_b => (operands::OperandExpression == operands::OperandIdentityBoolean::value_type) + // explicit cast(operands::OperandExpression => bool) + static_cast(type_or_dispatched_type::get(lhs)) + } + }; + } + + // operands::OperandIdentityBoolean::value_type{...} == bool + template< + // not allowed to be a candidate for an expression such as `"xxx" == xxx`, must be `"xxx"_b == xxx` + type_t L, + type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator==(const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + return rhs == lhs; + } + + // OperandIdentityString + + // string == operands::OperandIdentityString{...} + template< + typename L, + // not allowed to be a candidate for an expression such as `xxx == "xxx"`, must be `xxx == "xxx"_s` + type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator==(const L& lhs, const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::EQUAL, + left_expression_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + type_or_dispatched_type::get(rhs) + } + }; + } + + // operands::OperandIdentityString{...} == string + template< + // not allowed to be a candidate for an expression such as `"xxx" == xxx`, must be `"xxx"_s == xxx` + type_t L, + typename R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator==(const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs == lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::EQUAL, + left_expression_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + type_or_dispatched_type::get(rhs) + } + }; + } + + + // ============================================ + // operator!= + // ============================================ + + // OperandValue + + // floating_point != value{...} + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R> + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator!=(const L& lhs, const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::NOT_APPROX, + left_expression_type, + typename right_expression_type::value_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + type_or_dispatched_type::get(rhs).value(), + std::numeric_limits::epsilon() + } + }; + } + + // value{...} != floating_point + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator!=(const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs != lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::NOT_APPROX, + typename left_expression_type::value_type, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs).value(), + type_or_dispatched_type::get(rhs), + std::numeric_limits::epsilon() + } + }; + } + + // not(floating_point) != value{...} + template< + constrain_against_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator!=(const L& lhs, const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::NOT_EQUAL, + left_expression_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + type_or_dispatched_type::get(rhs).value() + } + }; + } + + // value{...} != not(floating_point) + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_against_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator!=(const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs != lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::NOT_EQUAL, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs).value(), + type_or_dispatched_type::get(rhs) + } + }; + } + + // OperandLiteralXxx + + // character != "xxx"_c + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator!=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::NOT_EQUAL, + left_expression_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + right_expression_type::value + } + }; + } + + // "xxx"_c != character + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator!=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs != lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::NOT_EQUAL, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + left_expression_type::value, + type_or_dispatched_type::get(rhs) + } + }; + } + + // integral != "xxx"_x + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator!=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::NOT_EQUAL, + left_expression_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + right_expression_type::value + } + }; + } + + // "xxx"_x != integral + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator!=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs != lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::NOT_EQUAL, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + left_expression_type::value, + type_or_dispatched_type::get(rhs) + } + }; + } + + // floating_point != "xxx"_x + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator!=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::NOT_APPROX, + left_expression_type, + typename right_expression_type::value_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + right_expression_type::value, + right_expression_type::epsilon + } + }; + } + + // "xxx"_x != floating_point + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator!=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs != lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::NOT_APPROX, + typename left_expression_type::value_type, + right_expression_type, + typename left_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + left_expression_type::value, + type_or_dispatched_type::get(rhs), + left_expression_type::epsilon + } + }; + } + + // ? != "xxx"_auto + template< + typename L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + // rebind-able type + and requires + { + typename lazy_expression_type_t, R>::template rebind< + lazy_expression_type_t, L> + >; + } + [[nodiscard]] constexpr auto operator!=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + // forward + // lhs != dispatched_expression{} + return lhs != dispatched_expression, dispatcher_type>{}; + } + + // "xxx"_auto != ? + template< + constrain_satisfied_type_or_dispatched_type_t L, + typename R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + // rebind-able type + and requires + { + typename lazy_expression_type_t, L>::template rebind< + lazy_expression_type_t, R> + >; + } + [[nodiscard]] constexpr auto operator!=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs != lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + // forward + // lhs != dispatched_expression{} + return dispatched_expression, dispatcher_type>{} != rhs; + } + + // OperandIdentityBoolean + + // bool != operands::OperandIdentityBoolean::value_type{...} + template< + type_or_dispatched_type_t L, + type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator!=(const L& lhs, const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + return dispatched_expression< + operands::OperandIdentityBoolean, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(rhs), + // (xxx == xxx) != "xxx"_b => (operands::OperandExpression != operands::OperandIdentityBoolean::value_type) + // explicit cast(operands::OperandExpression => bool) + not static_cast(type_or_dispatched_type::get(lhs)) + } + }; + } + + // operands::OperandIdentityBoolean::value_type{...} != bool + template< + type_or_dispatched_type_t L, + type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator!=(const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + return rhs != lhs; + } + + // OperandIdentityString + + // string != operands::OperandIdentityString{...} + template< + typename L, + // not allowed to be a candidate for an expression such as `xxx != "xxx"`, must be `xxx != "xxx"_s` + type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator!=(const L& lhs, const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::NOT_EQUAL, + left_expression_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + type_or_dispatched_type::get(rhs) + } + }; + } + + // operands::OperandIdentityString{...} != string + template< + // not allowed to be a candidate for an expression such as `"xxx" != xxx`, must be `"xxx"_s != xxx` + type_t L, + typename R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator!=(const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs != lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::NOT_EQUAL, + left_expression_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + type_or_dispatched_type::get(rhs) + } + }; + } + + // ============================================ + // operator> + // ============================================ + + // OperandValue + + // floating_point > value{...} + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>(const L& lhs, const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_THAN, + left_expression_type, + typename right_expression_type::value_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + type_or_dispatched_type::get(rhs).value(), + std::numeric_limits::epsilon() + } + }; + } + + // value{...} > floating_point + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>(const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs > lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_THAN, + typename left_expression_type::value_type, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs).value(), + type_or_dispatched_type::get(rhs), + std::numeric_limits::epsilon() + } + }; + } + + // not(floating_point) > value{...} + template< + constrain_against_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>(const L& lhs, const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_THAN, + left_expression_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + type_or_dispatched_type::get(rhs).value() + } + }; + } + + // value{...} > not(floating_point) + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_against_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>(const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs > lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_THAN, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs).value(), + type_or_dispatched_type::get(rhs) + } + }; + } + + // OperandLiteralXxx + + // character > "xxx"_c + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_THAN, + left_expression_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + right_expression_type::value + } + }; + } + + // "xxx"_c > character + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs > lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_THAN, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + left_expression_type::value, + type_or_dispatched_type::get(rhs) + } + }; + } + + // integral > "xxx"_x + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_THAN, + left_expression_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + right_expression_type::value + } + }; + } + + // "xxx"_x > integral + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs > lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_THAN, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + left_expression_type::value, + type_or_dispatched_type::get(rhs) + } + }; + } + + // floating_point > "xxx"_x + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_THAN, + left_expression_type, + typename right_expression_type::value_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + right_expression_type::value, + right_expression_type::epsilon + } + }; + } + + // "xxx"_x > floating_point + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs > lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_THAN, + typename left_expression_type::value_type, + right_expression_type, + typename left_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + left_expression_type::value, + type_or_dispatched_type::get(rhs), + left_expression_type::epsilon + } + }; + } + + // ? > "xxx"_auto + template< + typename L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + // rebind-able type + and requires + { + typename lazy_expression_type_t, R>::template rebind< + lazy_expression_type_t, L> + >; + } + [[nodiscard]] constexpr auto operator>(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + // forward + // lhs > dispatched_expression{} + return lhs > dispatched_expression, dispatcher_type>{}; + } + + // "xxx"_auto > ? + template< + constrain_satisfied_type_or_dispatched_type_t L, + typename R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + // rebind-able type + and requires + { + typename lazy_expression_type_t, L>::template rebind< + lazy_expression_type_t, R> + >; + } + [[nodiscard]] constexpr auto operator>([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs > lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + // forward + // lhs > dispatched_expression{} + return dispatched_expression, dispatcher_type>{} > rhs; + } + + // ============================================ + // operator>= + // ============================================ + + // OperandValue + + // floating_point >= value{...} + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>=(const L& lhs, const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_EQUAL, + left_expression_type, + typename right_expression_type::value_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + type_or_dispatched_type::get(rhs).value(), + std::numeric_limits::epsilon() + } + }; + } + + // value{...} >= floating_point + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>=(const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs >= lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_EQUAL, + typename left_expression_type::value_type, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs).value(), + type_or_dispatched_type::get(rhs), + std::numeric_limits::epsilon() + } + }; + } + + // not(floating_point) >= value{...} + template< + constrain_against_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>=(const L& lhs, const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_EQUAL, + left_expression_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + type_or_dispatched_type::get(rhs).value() + } + }; + } + + // value{...} >= not(floating_point) + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_against_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>=(const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs >= lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_EQUAL, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs).value(), + type_or_dispatched_type::get(rhs) + } + }; + } + + // OperandLiteralXxx + + // character >= "xxx"_c + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_EQUAL, + left_expression_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + right_expression_type::value + } + }; + } + + // "xxx"_c >= character + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs >= lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_EQUAL, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + left_expression_type::value, + type_or_dispatched_type::get(rhs) + } + }; + } + + // integral >= "xxx"_x + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_EQUAL, + left_expression_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + right_expression_type::value + } + }; + } + + // "xxx"_x >= integral + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs >= lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_EQUAL, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + left_expression_type::value, + type_or_dispatched_type::get(rhs) + } + }; + } + + // floating_point >= "xxx"_x + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_EQUAL, + left_expression_type, typename + right_expression_type::value_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + right_expression_type::value, + right_expression_type::epsilon + } + }; + } + + // "xxx"_x >= floating_point + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator>=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs >= lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::GREATER_EQUAL, + typename left_expression_type::value_type, + right_expression_type, + typename left_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + left_expression_type::value, + type_or_dispatched_type::get(rhs), + left_expression_type::epsilon + } + }; + } + + // ? >= "xxx"_auto + template< + typename L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + // rebind-able type + and requires + { + typename lazy_expression_type_t, R>::template rebind< + lazy_expression_type_t, L> + >; + } + [[nodiscard]] constexpr auto operator>=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + // forward + // lhs >= dispatched_expression{} + return lhs >= dispatched_expression, dispatcher_type>{}; + } + + // "xxx"_auto >= ? + template< + constrain_satisfied_type_or_dispatched_type_t L, + typename R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + // rebind-able type + and requires + { + typename lazy_expression_type_t, L>::template rebind< + lazy_expression_type_t, R> + >; + } + [[nodiscard]] constexpr auto operator>=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs >= lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + // forward + // lhs >= dispatched_expression{} + return dispatched_expression, dispatcher_type>{} >= rhs; + } + + // ============================================ + // operator< + // ============================================ + + // OperandValue + + // floating_point < value{...} + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<(const L& lhs, const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_THAN, + left_expression_type, + typename right_expression_type::value_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + type_or_dispatched_type::get(rhs).value(), + std::numeric_limits::epsilon() + } + }; + } + + // value{...} < floating_point + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<(const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs < lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_THAN, + typename left_expression_type::value_type, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs).value(), + type_or_dispatched_type::get(rhs), + std::numeric_limits::epsilon() + } + }; + } + + // not(floating_point) < value{...} + template< + constrain_against_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<(const L& lhs, const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_THAN, + left_expression_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + type_or_dispatched_type::get(rhs).value() + } + }; + } + + // value{...} < not(floating_point) + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_against_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<(const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs < lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_THAN, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs).value(), + type_or_dispatched_type::get(rhs) + } + }; + } + + // OperandLiteralXxx + + // character < "xxx"_c + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_THAN, + left_expression_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + right_expression_type::value + } + }; + } + + // "xxx"_c < character + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs < lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_THAN, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + left_expression_type::value, + type_or_dispatched_type::get(rhs) + } + }; + } + + // integral < "xxx"_x + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_THAN, + left_expression_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + right_expression_type::value + } + }; + } + + // "xxx"_x < integral + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs < lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_THAN, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + left_expression_type::value, + type_or_dispatched_type::get(rhs) + } + }; + } + + // floating_point < "xxx"_x + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_THAN, + left_expression_type, + typename right_expression_type::value_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + right_expression_type::value, + right_expression_type::epsilon + } + }; + } + + // "xxx"_x < floating_point + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs < lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_THAN, + typename left_expression_type::value_type, + right_expression_type, + typename left_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + left_expression_type::value, + type_or_dispatched_type::get(rhs), + left_expression_type::epsilon + } + }; + } + + // ? < "xxx"_auto + template< + typename L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + // rebind-able type + and requires + { + typename lazy_expression_type_t, R>::template rebind< + lazy_expression_type_t, L> + >; + } + [[nodiscard]] constexpr auto operator<(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + // forward + // lhs < dispatched_expression{} + return lhs < dispatched_expression, dispatcher_type>{}; + } + + // "xxx"_auto < ? + template< + constrain_satisfied_type_or_dispatched_type_t L, + typename R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + // rebind-able type + and requires + { + typename lazy_expression_type_t, L>::template rebind< + lazy_expression_type_t, R> + >; + } + [[nodiscard]] constexpr auto operator<([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs < lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + // forward + //dispatched_expression{} < rhs + return dispatched_expression, dispatcher_type>{} < rhs; + } + + // ============================================ + // operator<= + // ============================================ + + // OperandValue + + // floating_point <= value{...} + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<=(const L& lhs, const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_EQUAL, + left_expression_type, + typename right_expression_type::value_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + type_or_dispatched_type::get(rhs).value(), + std::numeric_limits::epsilon() + } + }; + } + + // value{...} <= floating_point + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<=(const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs <= lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_EQUAL, + typename left_expression_type::value_type, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs).value(), + type_or_dispatched_type::get(rhs), + std::numeric_limits::epsilon() + } + }; + } + + // not(floating_point) <= value{...} + template< + constrain_against_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<=(const L& lhs, const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_EQUAL, + left_expression_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + type_or_dispatched_type::get(rhs).value() + } + }; + } + + // value{...} <= not(floating_point) + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_against_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<=(const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs <= lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_EQUAL, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs).value(), + type_or_dispatched_type::get(rhs) + } + }; + } + + // OperandLiteralXxx + + // character <= "xxx"_c + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_EQUAL, + left_expression_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + right_expression_type::value + } + }; + } + + // "xxx"_c <= character + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs <= lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_EQUAL, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + left_expression_type::value, + type_or_dispatched_type::get(rhs) + } + }; + } + + // integral <= "xxx"_x + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_EQUAL, + left_expression_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + right_expression_type::value + } + }; + } + + // "xxx"_x <= integral + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs <= lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_EQUAL, + typename left_expression_type::value_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + left_expression_type::value, + type_or_dispatched_type::get(rhs) + } + }; + } + + // floating_point <= "xxx"_x + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_EQUAL, + left_expression_type, + typename right_expression_type::value_type, + typename right_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + right_expression_type::value, + right_expression_type::epsilon + } + }; + } + + // "xxx"_x <= floating_point + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator<=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs <= lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LESS_EQUAL, + typename left_expression_type::value_type, + right_expression_type, + typename left_expression_type::value_type + >, + dispatcher_type + >{ + .expression = { + left_expression_type::value, + type_or_dispatched_type::get(rhs), + left_expression_type::epsilon + } + }; + } + + // ? <= "xxx"_auto + template< + typename L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + // rebind-able type + and requires + { + typename lazy_expression_type_t, R>::template rebind< + lazy_expression_type_t, L> + >; + } + [[nodiscard]] constexpr auto operator<=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + // forward + // lhs <= dispatched_expression{} + return lhs <= dispatched_expression, dispatcher_type>{}; + } + + // "xxx"_auto <= ? + template< + constrain_satisfied_type_or_dispatched_type_t L, + typename R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + // rebind-able type + and requires + { + typename lazy_expression_type_t, L>::template rebind< + lazy_expression_type_t, R> + >; + } + [[nodiscard]] constexpr auto operator<=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // + { + // forward + // return rhs <= lhs; + + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + // forward + // dispatched_expression{} <= rhs + return dispatched_expression, dispatcher_type>{} <= rhs; + } + + // ============================================ + // operator and & operator or + // ============================================ + + // ? and ? + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator and(const L& lhs, const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LOGICAL_AND, + left_expression_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + type_or_dispatched_type::get(rhs) + } + }; + } + + // ? or ? + template< + constrain_satisfied_type_or_dispatched_type_t L, + constrain_satisfied_type_or_dispatched_type_t R + > + // can we trust users not to (inadvertently) mess up the ADL? vvv + requires(is_dispatched_expression_v or is_dispatched_expression_v) + [[nodiscard]] constexpr auto operator or(const L& lhs, const R& rhs) noexcept -> auto // + { + using dispatcher_type = lazy_dispatcher_type_t, L, R>; + + using left_expression_type = lazy_expression_type_t, L>; + using right_expression_type = lazy_expression_type_t, R>; + + return dispatched_expression< + operands::OperandExpression< + operands::ExpressionCategory::LOGICAL_OR, + left_expression_type, + right_expression_type + >, + dispatcher_type + >{ + .expression = { + type_or_dispatched_type::get(lhs), + type_or_dispatched_type::get(rhs) + } + }; + } + + template + auto register_event(EventType&& event) noexcept(false) -> decltype(auto) + { + if constexpr (std::is_same_v) + { + return + executor::Executor::instance() + .on(std::forward(event)); + } + else + { + return + executor::Worker::instance() + .on(std::forward(event)); + } + } + + template + class ExpressionDispatcher // NOLINT(bugprone-crtp-constructor-accessibility) + { + public: + template + [[nodiscard]] constexpr auto operator%(Lhs&& lhs) const noexcept -> dispatched_expression // + { + return {.expression = std::forward(lhs)}; + } + }; + + class DispatcherThat final : public ExpressionDispatcher + { + public: + using ExpressionDispatcher::operator%; + }; + + // more dispatched expression vvv + // ... + + class ExpectResult final + { + public: + struct fatal {}; + + private: + template + struct with_location + { + std::source_location location; + + constexpr explicit(false) with_location(const T&, const std::source_location& l = std::source_location::current()) noexcept + : location{l} {} + }; + + bool result_; + + public: + constexpr explicit ExpectResult(const bool result) noexcept + : result_{result} {} + + template + constexpr auto operator<<(MessageType&& message) noexcept -> ExpectResult& // + requires requires { events::EventLog{.message = std::forward(message)}; } + { + if (not result_) + { + dispatcher::register_event(events::EventLog{.message = std::forward(message)}); + } + + return *this; + } + + constexpr auto operator<<(const with_location& location) noexcept(false) -> ExpectResult& + { + if (not result_) + { + register_event(events::EventAssertionFatal{.location = location.location}); + } + + return *this; + } + }; + + class DispatcherExpect final + { + public: + template + requires(is_expression_v or is_dispatched_expression_v) + constexpr auto operator()( + Expression&& expression, + const std::source_location& location = std::source_location::current() + ) const noexcept(false) -> ExpectResult + { + if constexpr (is_dispatched_expression_v) + { + // workaround if needed + // using dispatcher_type = typename Expression::dispatcher_type; + + const auto result = dispatcher::register_event( + events::EventAssertion{ + .expression = std::forward(expression).expression, + .location = location + } + ); + + return ExpectResult{result}; + } + else + { + return ExpectResult + { + dispatcher::register_event( + events::EventAssertion{ + .expression = std::forward(expression), + .location = location + } + ) + }; + } + } + }; + + namespace dispatcher_detail + { + template + class DispatcherTestBase // NOLINT(bugprone-crtp-constructor-accessibility) + { + protected: + test_categories_type categories_; + + public: + template + constexpr auto operator=(InvocableType&& invocable) & noexcept(false) -> DispatcherTestBase& + { + dispatcher::register_event( + events::EventTest{ + .name = static_cast(*this).name(), + .categories = categories_, + .invocable = std::forward(invocable), + .arg = {} + } + ); + + return *this; + } + + template + constexpr auto operator=(InvocableType&& invocable) && noexcept(false) -> DispatcherTestBase& + { + dispatcher::register_event( + events::EventTest{ + .name = static_cast(*this).name(), + .categories = std::move(categories_), + .invocable = std::forward(invocable), + .arg = {} + } + ); + + return *this; + } + + private: + // string literal + template + constexpr auto do_push(const char (&string)[N]) noexcept -> void + { + categories_.emplace_back(string); + } + + // string literal + constexpr auto do_push(const char* string) noexcept -> void + { + categories_.emplace_back(string); + } + + // string/string_view + constexpr auto do_push(const std::string_view string) noexcept -> void + { + categories_.emplace_back(string); + } + + // string&& + constexpr auto do_push(std::string&& string) noexcept -> void + { + categories_.emplace_back(std::move(string)); + } + + // const vector& + constexpr auto do_push(const test_categories_view_type categories) noexcept -> void + { + #if __has_cpp_attribute(cpp_lib_containers_ranges) and cpp_lib_containers_ranges >= 202202L + categories_.append_range(categories.get()); + #else + categories_.reserve(categories_.size() + categories.get().size()); + categories_.insert(categories_.end(), categories.get().begin(), categories.get().end()); + #endif + } + + // vector&& + constexpr auto do_push(test_categories_type&& categories) noexcept -> void + { + #if __has_cpp_attribute(cpp_lib_containers_ranges) and cpp_lib_containers_ranges >= 202202L + categories_.append_range(std::move(categories)); + #else + categories_.reserve(categories_.size() + categories.size()); + categories_.insert(categories_.end(), std::make_move_iterator(categories.begin()), std::make_move_iterator(categories.end())); + // silence warning + (void)std::move(categories); + #endif + } + + [[nodiscard]] constexpr auto do_move() && noexcept -> D&& + { + return std::move(*static_cast(this)); + } + + public: + template + [[nodiscard]] constexpr auto operator[](Args&&... args) && noexcept -> D&& + { + (this->do_push(std::forward(args)), ...); + + return std::move(*this).do_move(); + } + }; + } + + template + class DispatcherSuite final + { + // fixme: lifetime + suite_name_view_type name_; + + public: + template + constexpr explicit(false) DispatcherSuite(InvocableType invocable) noexcept // + requires requires { +invocable; } + { + register_event( + events::EventSuite + { + .name = StringLiteral.template as(), + .suite = +invocable + } + ); + } + }; + + template + class DispatcherTestLiteral final : public dispatcher_detail::DispatcherTestBase> + { + friend class dispatcher_detail::DispatcherTestBase; + + [[nodiscard]] constexpr auto name() const noexcept -> test_name_view_type + { + std::ignore = this; + return StringLiteral.template as(); + } + + public: + using dispatcher_detail::DispatcherTestBase::operator=; + }; + + class DispatcherTest final : public dispatcher_detail::DispatcherTestBase + { + friend class DispatcherTestBase; + + [[nodiscard]] constexpr auto name() const noexcept -> test_name_view_type + { + return name_; + } + + test_name_view_type name_; + + public: + using DispatcherTestBase::operator=; + + constexpr explicit DispatcherTest(const test_name_view_type name) noexcept + : name_{name} {} + }; +} diff --git a/src/unit_test/events.hpp b/src/unit_test/events.hpp new file mode 100644 index 00000000..19e3b2d2 --- /dev/null +++ b/src/unit_test/events.hpp @@ -0,0 +1,322 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +#include + +#include + +namespace gal::prometheus::unit_test::events +{ + class Event {}; + + template + constexpr auto is_event_v = std::is_base_of_v; + template + concept event_t = is_event_v; + + #if defined(GAL_PROMETHEUS_COMPILER_GNU) or defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH + + // struct bar {}; + // struct foo : bar + // { + // int a; + // }; + // foo f{.a = 42}; // <-- warning: missing initializer for member `foo::` [-Wmissing-field-initializers] + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(-Wmissing-field-initializers) + #endif + + // ========================================= + // SUITE + // ========================================= + + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventSuiteBegin final : public Event + { + public: + suite_name_view_type name; + }; + + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventSuiteEnd final : public Event + { + public: + suite_name_view_type name; + }; + + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventSuite final : public Event + { + public: + using suite_type = void (*)(); + + suite_name_view_type name; + suite_type suite; + + // throwable: end suite + constexpr auto operator()() noexcept(false) -> void + { + std::invoke(suite); + } + + // throwable: end suite + constexpr auto operator()() const noexcept(false) -> void + { + std::invoke(suite); + } + + private: + [[nodiscard]] constexpr explicit operator EventSuiteBegin() const noexcept + { + return {.name = name}; + } + + [[nodiscard]] constexpr explicit operator EventSuiteEnd() const noexcept + { + return {.name = name}; + } + + public: + [[nodiscard]] constexpr auto begin() const noexcept -> EventSuiteBegin + { + return this->operator EventSuiteBegin(); + } + + [[nodiscard]] constexpr auto end() const noexcept -> EventSuiteEnd + { + return this->operator EventSuiteEnd(); + } + }; + + // ========================================= + // TEST + // ========================================= + + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventTestBegin final : public Event + { + public: + test_name_view_type name; + }; + + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventTestSkip final : public Event + { + public: + test_name_view_type name; + }; + + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventTestEnd final : public Event + { + public: + test_name_view_type name; + }; + + struct none {}; + + template + requires std::is_invocable_v or std::is_invocable_v + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventTest final : public Event + { + public: + using invocable_type = InvocableType; + using arg_type = Arg; + + test_name_view_type name; + test_categories_type categories; + + mutable invocable_type invocable; + mutable arg_type arg; + + // throwable: end test + constexpr auto operator()() const noexcept(false) -> void + { + return [](I&& i, A&& a) noexcept(false) -> void + { + if constexpr (requires { std::invoke(std::forward(i)); }) + { + std::invoke(std::forward(i)); + } + else if constexpr (requires { std::invoke(std::forward(i), std::forward(a)); }) + { + std::invoke(std::forward(i), std::forward(a)); + } + else if constexpr (requires { std::invoke(i.template operator()()); }) + { + std::invoke(i.template operator()()); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + }(invocable, arg); + } + + private: + [[nodiscard]] constexpr explicit operator EventTestBegin() const noexcept + { + return {.name = name}; + } + + [[nodiscard]] constexpr explicit operator EventTestEnd() const noexcept + { + return {.name = name}; + } + + [[nodiscard]] constexpr explicit operator EventTestSkip() const noexcept + { + return {.name = name}; + } + + public: + [[nodiscard]] constexpr auto begin() const noexcept -> EventTestBegin + { + return this->operator EventTestBegin(); + } + + [[nodiscard]] constexpr auto end() const noexcept -> EventTestEnd + { + return this->operator EventTestEnd(); + } + + [[nodiscard]] constexpr auto skip() const noexcept -> EventTestSkip + { + return this->operator EventTestSkip(); + } + }; + + // ========================================= + // ASSERTION + // ========================================= + + template + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventAssertionPass final : public Event + { + public: + using expression_type = Expression; + + expression_type expression; + std::source_location location; + }; + + template + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventAssertionFail final : public Event + { + public: + using expression_type = Expression; + + expression_type expression; + std::source_location location; + }; + + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventAssertionFatal final : public Event + { + public: + std::source_location location; + }; + + template + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventAssertion final : public Event + { + public: + using expression_type = Expression; + + expression_type expression; + std::source_location location; + + private: + [[nodiscard]] constexpr explicit operator EventAssertionPass() const noexcept + { + return {.expression = expression, .location = location}; + } + + [[nodiscard]] constexpr explicit operator EventAssertionFail() const noexcept + { + return {.expression = expression, .location = location}; + } + + [[nodiscard]] constexpr explicit operator EventAssertionFatal() const noexcept + { + return {.location = location}; + } + + public: + [[nodiscard]] constexpr auto pass() const noexcept -> EventAssertionPass + { + // fixme: Compiler Error: C2273 + // return this->operator EventAssertionPass(); + return operator EventAssertionPass(); + } + + [[nodiscard]] constexpr auto fail() const noexcept -> EventAssertionFail + { + // fixme: Compiler Error: C2273 + // return this->operator EventAssertionFail(); + return operator EventAssertionFail(); + } + + [[nodiscard]] constexpr auto fatal() const noexcept -> EventAssertionFatal + { + // fixme: Compiler Error: C2273 + // return this->operator EventAssertionFatal(); + return operator EventAssertionFatal(); + } + }; + + // ========================================= + // UNEXPECTED + // ========================================= + + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventUnexpected final : public Event + { + public: + std::string_view message; + + [[nodiscard]] constexpr auto what() const noexcept -> std::string_view + { + return message; + } + }; + + // ========================================= + // LOG + // ========================================= + + template + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventLog final : public Event + { + public: + using message_type = MessageType; + + message_type message; + }; + + // ReSharper disable CppInconsistentNaming + EventLog(const char*) -> EventLog>; + template + EventLog(const char (&)[N]) -> EventLog>; + EventLog(const wchar_t*) -> EventLog>; + template + EventLog(const wchar_t (&)[N]) -> EventLog>; + EventLog(const char8_t*) -> EventLog>; + template + EventLog(const char8_t (&)[N]) -> EventLog>; + EventLog(const char16_t*) -> EventLog>; + template + EventLog(const char16_t (&)[N]) -> EventLog>; + EventLog(const char32_t*) -> EventLog>; + template + EventLog(const char32_t (&)[N]) -> EventLog>; + // ReSharper restore CppInconsistentNaming + + // ========================================= + // SUMMARY + // ========================================= + + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventSummary final : public Event {}; + + #if defined(GAL_PROMETHEUS_COMPILER_GNU) or defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH + #endif +} diff --git a/src/unit_test/executor.hpp b/src/unit_test/executor.hpp new file mode 100644 index 00000000..e15342ea --- /dev/null +++ b/src/unit_test/executor.hpp @@ -0,0 +1,1346 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include + +#if __has_include() +#include +#else +#include +#include +#endif + +#include + +#include +#if not(__has_cpp_attribute(__cpp_explicit_this_parameter) and __cpp_explicit_this_parameter >= 202110L) +#include +#endif +#include + +#include +#include + +namespace gal::prometheus::unit_test::executor +{ + /** + * Executor: + * size_t max_worker = std::thread::hardware_concurrency() + * thread workers[max_worker] + * vector suites + * vector suite_results + * + * worker(thread:0) => (results[ 0 + 0 * max_worker], results[ 0 + 1 * max_worker], ..., results[ 0 + (n-1) * max_worker]) + * worker(thread:1) => (results[ 1 + 0 * max_worker], results[ 1 + 1 * max_worker], ..., results[ 1 + (n-1) * max_worker]) + * worker(thread:2) => (results[ 2 + 0 * max_worker], results[ 2 + 1 * max_worker], ..., results[ 2 + (n-1) * max_worker]) + * ... + * worker(thread:n-1) => (results[(n-1) + 0 * max_worker], results[(n-1) + 1 * max_worker], ..., results[(n-1) + (n-1) * max_worker]) + */ + + struct internal_tag {}; + + class Worker; + + class Executor + { + // report_failure() + // config_xxx(...) + friend class Worker; + + public: + using worker_pool_type = std::vector; + using suites_type = std::vector; + + private: + config_type config_; + + worker_pool_type worker_pool_; + // for print + constexpr static auto worker_job_done = std::numeric_limits::max(); + std::vector worker_job_tracer_; + + suites_type suites_; + suite_results_type suite_results_; + + // The total number of errors that have been reported, + // the number of errors that will be reported by each worker at the end of their work + std::size_t total_fails_; + // When a worker reports the number of failures, temporarily block all threads to create a new worker, + // because the error may have reached its limit, and all subsequent suites should be skipped + std::mutex mutex_reporting_; + + // ========================================= + // ERROR + // ========================================= + + [[nodiscard]] auto is_executor_fatal_error() const noexcept -> bool + { + return total_fails_ == std::numeric_limits::max(); + } + + auto make_executor_fatal_error() noexcept -> void + { + total_fails_ = std::numeric_limits::max(); + } + + // called by worker + // CONTINUE: false + // TERMINATE: true + // todo: + // There is no easy and safe way to simply terminate all workers when one of them reports an error and reaches a specified threshold. + // We currently have to wait for each worker to report an error or finish the job. + auto report_failure() noexcept -> bool + { + if (const auto terminate = [this]() noexcept -> bool + { + if (is_executor_fatal_error()) + { + return true; + } + + std::scoped_lock lock{mutex_reporting_}; + total_fails_ += 1; + return total_fails_ >= config_n_failures_abort(); + }(); + terminate) + [[unlikely]] + { + make_executor_fatal_error(); + + return true; + } + + return false; + } + + // ========================================= + // CONFIG + // ========================================= + + [[nodiscard]] auto config() const noexcept -> const config_type& + { + return config_; + } + + [[nodiscard]] auto config_output_color() const noexcept -> const config_type::color_type& + { + return config().color; + } + + [[nodiscard]] auto config_get_ident_size(const std::size_t nested_level) const noexcept -> std::size_t + { + return nested_level * config().tab_width; + } + + [[nodiscard]] auto config_output_prefix() const noexcept -> std::string_view + { + return config().prefix; + } + + auto config_out() noexcept -> void + { + config().out(std::move(suite_results_)); + } + + template + [[nodiscard]] auto config_check_report_level() const noexcept -> bool + { + return (RequiredLevel & config().report_level) == RequiredLevel; + } + + [[nodiscard]] auto config_dry_run() const noexcept -> bool + { + return config().dry_run; + } + + template + [[nodiscard]] auto config_check_break_point() const noexcept -> bool + { + return static_cast(RequiredLevel & config().break_point_level); + } + + [[nodiscard]] auto config_n_failures_abort() const noexcept -> std::size_t + { + return config().abort_after_n_failures; + } + + [[nodiscard]] auto config_check_suite_execute(const suite_node_type& node) const noexcept -> bool + { + return config().filter_suite(node); + } + + [[nodiscard]] auto config_check_test_execute(const test_node_type& node) const noexcept -> bool + { + return config().filter_test(node); + } + + // ========================================= + // SUITE + // ========================================= + + // Create a new worker in the current thread, execute the given suite, and write the result to suite_result + [[nodiscard]] auto worker_work(const events::EventSuite& suite, suite_result_type& suite_result) noexcept -> bool; + + // Each thread processes the suite in the corresponding bucket + auto worker_thread_func(std::size_t thread_index, std::size_t step) noexcept -> void; + + // ========================================= + // SUMMARY + // ========================================= + + auto on(const events::EventSummary&) noexcept -> void + { + struct total_result + { + std::size_t test_passed; + std::size_t test_failed; + std::size_t test_skipped; + + std::size_t assertion_passed; + std::size_t assertion_failed; + + constexpr auto operator+(const total_result& other) const noexcept -> total_result + { + total_result new_one{*this}; + + new_one.test_passed += other.test_passed; + new_one.test_failed += other.test_failed; + new_one.test_skipped += other.test_skipped; + + new_one.assertion_passed += other.assertion_passed; + new_one.assertion_failed += other.assertion_failed; + + return new_one; + } + }; + + #define GET_RESULT_OF_TEST_FUNCTION_BODY \ + const auto passed = static_cast(\ + +(\ + test_result.status == test_result_type::Status::PASSED\ + )\ + );\ + const auto failed = static_cast(\ + +(\ + test_result.status == test_result_type::Status::FAILED or\ + test_result.status == test_result_type::Status::INTERRUPTED or\ + test_result.status == test_result_type::Status::TERMINATED\ + )\ + );\ + const auto skipped = static_cast(\ + +(\ + test_result.status == test_result_type::Status::SKIPPED_NO_ASSERTION or\ + test_result.status == test_result_type::Status::SKIPPED_FILTERED\ + )\ + );\ + \ + return std::ranges::fold_left(\ + test_result.children,\ + total_result\ + {\ + .test_passed = passed,\ + .test_failed = failed,\ + .test_skipped = skipped,\ + .assertion_passed = test_result.total_assertions_passed,\ + .assertion_failed = test_result.total_assertions_failed\ + },\ + [self](const total_result& total, const test_result_type& nested_test_result) noexcept -> total_result\ + {\ + return total + self(nested_test_result);\ + }\ + ); + + + #if __has_cpp_attribute(__cpp_explicit_this_parameter) and __cpp_explicit_this_parameter >= 202110L + constexpr auto get_result_of_test = [](this const auto& self, const test_result_type& test_result) noexcept -> total_result + { + GET_RESULT_OF_TEST_FUNCTION_BODY + }; + #else + constexpr auto get_result_of_test = functional::y_combinator + { + [](const auto& self, const test_result_type& test_result) noexcept -> total_result + { + GET_RESULT_OF_TEST_FUNCTION_BODY + } + }; + #endif + + #undef GET_RESULT_OF_TEST_FUNCTION_BODY + + constexpr auto get_result_of_suite = [get_result_of_test](const suite_result_type& suite_result) noexcept -> total_result + { + return std::ranges::fold_left( + suite_result.results, + total_result + { + .test_passed = 0, + .test_failed = 0, + .test_skipped = 0, + .assertion_passed = 0, + .assertion_failed = 0 + }, + [get_result_of_test](const total_result& total, const test_result_type& test_result) noexcept -> total_result + { + return total + get_result_of_test(test_result); + } + ); + }; + + std::ranges::for_each( + suite_results_, + [this, get_result_of_suite](suite_result_type& suite_result) noexcept -> void + { + const auto color = config_output_color(); + + // ReSharper disable once CppUseStructuredBinding + if (const auto result = get_result_of_suite(suite_result); + result.assertion_failed == 0) + [[likely]] + { + if (result.assertion_passed == 0) + { + // skip empty suite report + } + else + { + std::format_to( + std::back_inserter(suite_result.report_string), + "\n==========================================\n" + "Suite {}{}{} -> {}all tests passed{}({} assertions in {} tests), {} tests skipped." + "\n==========================================\n", + color.suite, + suite_result.name, + color.none, + color.pass, + color.none, + result.assertion_passed, + result.test_passed, + result.test_skipped + ); + } + } + else + [[unlikely]] + { + std::format_to( + std::back_inserter(suite_result.report_string), + "\n==========================================\n" + "Suite {}{}{}\n" + "tests {} | {} {}passed({:.6g}%){} | {} {}failed({:.6g}%){} | {} {}skipped({:.6g}%){}\n" + "assertions {} | {} {}passed({:.6g}%){} | {} {}failed({:.6g}%){}" + "\n==========================================\n", + color.suite, + suite_result.name, + color.none, + // test + result.test_passed + result.test_failed + result.test_skipped, + // passed + result.test_passed, + color.pass, + static_cast(result.test_passed) / + static_cast(result.test_passed + result.test_failed + result.test_skipped) + * 100.0, + color.none, + // failed + result.test_failed, + color.failure, + static_cast(result.test_failed) / + static_cast(result.test_passed + result.test_failed + result.test_skipped) + * 100.0, + color.none, + // skipped + result.test_skipped, + color.skip, + static_cast(result.test_skipped) / + static_cast(result.test_passed + result.test_failed + result.test_skipped) + * 100.0, + color.none, + // assertion + result.assertion_passed + result.assertion_failed, + // passed + result.assertion_passed, + color.pass, + static_cast(result.assertion_passed) / + static_cast(result.assertion_passed + result.assertion_failed) + * 100.0, + color.none, + // failed + result.assertion_failed, + color.failure, + static_cast(result.assertion_failed) / + static_cast(result.assertion_passed + result.assertion_failed) + * 100.0, + color.none + ); + } + } + ); + + config_out(); + } + + explicit Executor() noexcept + : + total_fails_{0} {} + + public: + Executor(const Executor&) noexcept = delete; + Executor(Executor&&) noexcept = delete; + auto operator=(const Executor&) noexcept -> Executor& = delete; + auto operator=(Executor&&) noexcept -> Executor& = delete; + + ~Executor() noexcept + { + if (not config_.dry_run) + { + // filter suite + const auto [removed_begin, removed_end] = std::ranges::remove_if( + suites_, + [this](const auto& suite) noexcept -> bool + { + return not this->config_check_suite_execute(suite_node_type{.name = suite.name}); + } + ); + suites_.erase(removed_begin, removed_end); + + // preallocate all suite result, avoid synchronise + suite_results_.resize(suites_.size()); + + // create thread + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(worker_pool_.empty()); + // - 1 => one thread for print + const auto max_worker = static_cast(std::thread::hardware_concurrency() - 1); + const auto real_workers = std::ranges::min(max_worker, suites_.size()); + worker_pool_.reserve(real_workers); + worker_job_tracer_.resize(real_workers, 0); + for (const auto index: std::views::iota(static_cast(0), real_workers)) + { + worker_pool_.emplace_back(&Executor::worker_thread_func, this, index, real_workers); + } + + #if defined(GAL_PROMETHEUS_PLATFORM_WINDOWS) + #define SYSTEM_CLEAR_CONSOLE std::system("cls"); // NOLINT(concurrency-mt-unsafe) + #else + #define SYSTEM_CLEAR_CONSOLE std::system("clear"); // NOLINT(concurrency-mt-unsafe) + #endif + + // join + std::atomic job_done = false; + std::thread{ + [this, &job_done]() noexcept -> void + { + struct info_type + { + std::size_t thread_index; + + std::size_t result_size; + std::string message; + }; + + std::vector current_infos{}; + current_infos.resize( + worker_job_tracer_.size(), + { + .thread_index = std::numeric_limits::max(), + .result_size = std::numeric_limits::max(), + .message = "" + } + ); + + while (not job_done) + { + // @see Executor::worker_thread_func + for (const auto [i, index]: std::views::enumerate(worker_job_tracer_)) + { + const auto& color = config_output_color(); + + if (auto& [thread_index, result_size, message] = current_infos[i]; + index == worker_job_done) + { + message = std::format( + "{}WORKER[{:>2}]{}: {}job done!{}", + color.fatal, + i, + color.none, + color.pass, + color.none + ); + } + else + { + if (const auto& suite_result = suite_results_[index]; + thread_index != index or result_size != suite_result.results.size()) + { + if (suite_result.results.empty()) + { + message = std::format( + "{}WORKER[{:>2}]{}: {}pending...{}", + color.fatal, + i, + color.none, + color.skip, + color.none + ); + } + else + { + thread_index = index; + result_size = suite_result.results.size(); + message = std::format( + "{}WORKER[{:>2}]{}: running test {}[{}] {}{}", + color.fatal, + i, + color.none, + color.test, + suite_result.name, + suite_result.results[result_size - 1].name, + color.none + ); + } + } + else + { + message.push_back('.'); + } + } + } + + // const auto total_length = std::ranges::fold_left( + // current_infos, + // // '\n' + // current_infos.size(), + // [](const std::size_t total, const info_type& info) noexcept -> std::size_t + // { + // return total + info.message.size(); + // } + // ); + // + // std::string output{}; + // output.reserve(total_length); + // std::ranges::for_each( + // current_infos, + // [&output](const info_type& info) noexcept -> void + // { + // output.append(info.message); + // output.push_back('\n'); + // } + // ); + // std::print(stdout, "{}", output); + + std::ranges::for_each( + current_infos, + [](const info_type& info) noexcept -> void + { + #if __has_include() + std::println(stdout, "{}", info.message); + #else + const auto output = std::format("{}", info.message); + std::fputs(output.c_str(), stdout); + std::putc('\n', stdout); + #endif + } + ); + + using namespace std::chrono_literals; + std::this_thread::sleep_for(1s); + + SYSTEM_CLEAR_CONSOLE + } + } + }.detach(); + std::ranges::for_each( + worker_pool_, + [](auto& thread) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(thread.joinable()); + thread.join(); + } + ); + + // ALL THREAD FINISHED + job_done = true; + SYSTEM_CLEAR_CONSOLE + + on(events::EventSummary{}); + } + } + + // ========================================= + // INSTANCE + // ========================================= + + [[nodiscard]] static auto instance() noexcept -> Executor& + { + static Executor executor{}; + return executor; + } + + // ========================================= + // CONFIG + // ========================================= + + auto set_config(config_type&& config) noexcept -> void + { + config_ = std::move(config); + } + + // ========================================= + // SUITE + // ========================================= + + auto on(const events::EventSuite& suite) noexcept -> void + { + suites_.push_back(suite); + } + }; + + class Worker + { + // run(suite, suite_result) + friend class Executor; + + // on(const events::EventAssertionFatal&) + class EndThisTest final {}; + + // check_total_failures() + class EndThisSuite final {}; + + public: + struct test_data_type + { + test_name_view_type name; + test_categories_view_type categories; + }; + + using test_data_stack_type = std::vector; + + // nullable + using test_results_iterator_type = test_results_type::pointer; + + private: + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + constexpr + #else + inline const + #endif + static auto worker_off_work = reinterpret_cast(0xbad'c0ffee); + + suite_result_type* suite_; + + test_data_stack_type test_data_stack_; + + test_results_iterator_type current_test_result_; + + // ========================================= + + [[nodiscard]] auto executor() const noexcept -> Executor& + { + std::ignore = this; + return Executor::instance(); + } + + [[nodiscard]] auto suite() noexcept -> suite_result_type& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(suite_ != nullptr); + return *suite_; + } + + [[nodiscard]] auto suite() const noexcept -> const suite_result_type& + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(suite_ != nullptr); + return *suite_; + } + + // ========================================= + + enum class IdentType : std::uint8_t + { + TEST, + ASSERTION, + }; + + template + [[nodiscard]] auto nested_level_of_current_test() const noexcept -> std::size_t + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not suite().results.empty()); + + if constexpr (Type == IdentType::ASSERTION) + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ != nullptr); + } + else + { + // top level + if (current_test_result_ == nullptr) + { + return 1; + } + } + + std::size_t result = 0; + for (const auto* p = current_test_result_; p != nullptr; p = p->parent) + { + result += 1; + } + + return result + (Type == IdentType::ASSERTION); + } + + template + [[nodiscard]] auto ident_size_of_current_test() const noexcept -> std::size_t + { + return executor().config_get_ident_size(nested_level_of_current_test()); + } + + // [suite_name] test1.test2.test3 + [[nodiscard]] auto fullname_of_current_test() const noexcept -> std::string + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not suite().results.empty()); + + auto result = std::format("[{}] ", suite().name); + + const auto* p = std::addressof(suite().results.back()); + while (p != nullptr) + { + result.append(p->name); + result.push_back('.'); + + p = p->children.empty() ? nullptr : std::addressof(p->children.back()); + } + + result.pop_back(); + return result; + } + + // ========================================= + + // run(const events::EventSuite&) => OK + // check_total_failures => ERROR + auto off_work() noexcept -> void + { + current_test_result_ = worker_off_work; + } + + [[nodiscard]] auto on_working() const noexcept -> bool + { + return current_test_result_ != worker_off_work; + } + + [[nodiscard]] auto off_working() const noexcept -> bool + { + return current_test_result_ == worker_off_work; + } + + auto check_total_failures() noexcept(false) -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ != nullptr); + + if (executor().report_failure()) + { + current_test_result_->status = test_result_type::Status::TERMINATED; + + const auto n_failures_abort = executor().config_n_failures_abort(); + const auto prefix = executor().config_output_prefix(); + const auto& color = executor().config_output_color(); + + std::format_to( + std::back_inserter(suite().report_string), + "{:<{}}{}The number of errors has reached the specified threshold {} " + "(this test raises {} error(s)), " + "terminate all suite/test!{}\n", + prefix, + ident_size_of_current_test(), + color.failure, + n_failures_abort, + current_test_result_->total_assertions_failed, + color.none + ); + + throw EndThisSuite{}; // NOLINT(hicpp-exception-baseclass) + } + } + + // ========================================= + // SUITE + // ========================================= + + auto run(const events::EventSuite& suite, suite_result_type& suite_result) noexcept -> void + { + // ================================== + // bind result + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ == nullptr); + suite_ = std::addressof(suite_result); + + // ================================== + // begin + this->suite().name = suite.name; + + if (executor().config_check_report_level()) + { + const auto& color = executor().config_output_color(); + + std::format_to( + std::back_inserter(this->suite().report_string), + "Executing suite {}{}{} vvv\n", + color.suite, + this->suite().name, + color.none + ); + } + + // ================================== + // run + try + { + std::invoke(suite); + } + catch (const EndThisSuite&) // NOLINT(bugprone-empty-catch) + { + // This exception is thrown by the function `check_total_failures()` and is intercepted here and no longer propagated + // do nothing here :) + } + // The user's suite threw an exception, but it was not handled. + // We capture the exception here to avoid early termination of the program. + // suite<"throw"> _ = [] + // { + // throw std::runtime_error{"throw!"}; + // + // "unreachable"_test = [] { }; + // }; + catch (const std::exception& exception) + { + on(events::EventUnexpected{.message = exception.what()}); + } + catch (...) + { + on(events::EventUnexpected{.message = "unknown exception type, not derived from std::exception"}); + } + + // ================================== + // end/error + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ == nullptr or off_working()); + + if (executor().config_check_report_level()) + { + const auto& color = executor().config_output_color(); + + std::format_to( + std::back_inserter(this->suite().report_string), + "^^^ End of suite {}{}{} execution\n", + color.suite, + this->suite().name, + color.none + ); + } + + off_work(); + } + + // ========================================= + // TEST + // ========================================= + + template + auto on(const events::EventTest& test, internal_tag) noexcept(false) -> void + { + if (not on_working()) + { + return; + } + + // build chain + test_node_type node{.parent = nullptr, .name = test.name, .categories = test.categories}; + { + auto* parent = std::addressof(node.parent); + std::ranges::for_each( + test_data_stack_ | std::views::reverse, + [&parent](const test_data_type& data) noexcept -> void + { + *parent = std::make_unique(nullptr, data.name, data.categories); + parent = std::addressof((*parent)->parent); + } + ); + } + + if (executor().config_check_test_execute(node)) + { + test_data_stack_.emplace_back(test.name, test.categories); + this->on(test.begin()); + + try + { + std::invoke(test); + } + catch (const EndThisTest&) // NOLINT(bugprone-empty-catch) + { + // This exception is thrown by and only by the function `on(events::EventAssertionFatal)` and is intercepted here and no longer propagated + // do nothing here :) + } + catch (const EndThisSuite&) // NOLINT(bugprone-empty-catch) + { + // end this test + this->on(test.end()); + off_work(); + // propagate exception + throw; + } + // The user's suite threw an exception, but it was not handled. + // We capture the exception here to avoid early termination of the program. + catch (const std::exception& exception) + { + on(events::EventUnexpected{.message = exception.what()}); + } + catch (...) + { + on(events::EventUnexpected{.message = "unhandled exception, not derived from std::exception"}); + } + + this->on(test.end()); + test_data_stack_.pop_back(); + } + else + { + this->on(test.skip()); + } + } + + auto on(const events::EventTestBegin& test_begin) noexcept -> void + { + // we chose to construct a temporary object here to avoid possible errors, and trust that the optimizer will forgive us ;) + auto t = test_result_type + { + .name = std::string{test_begin.name}, + .parent = current_test_result_, + .children = {}, + .total_assertions_passed = 0, + .total_assertions_failed = 0, + .time = {}, + .status = test_result_type::Status::PENDING + }; + + if (current_test_result_) + { + // nested + auto& this_test = current_test_result_->children.emplace_back(std::move(t)); + current_test_result_ = std::addressof(this_test); + + if (executor().config_check_report_level()) + { + const auto prefix = executor().config_output_prefix(); + const auto& color = executor().config_output_color(); + + std::format_to( + std::back_inserter(suite().report_string), + "{:<{}}Running nested test {}{}{}...\n", + prefix, + ident_size_of_current_test(), + color.test, + fullname_of_current_test(), + color.none + ); + } + } + else + { + // top level + auto& this_test = suite().results.emplace_back(std::move(t)); + current_test_result_ = std::addressof(this_test); + + if (executor().config_check_report_level()) + { + const auto prefix = executor().config_output_prefix(); + const auto& color = executor().config_output_color(); + + std::format_to( + std::back_inserter(suite().report_string), + "{:<{}}Running test {}{}{}...\n", + prefix, + ident_size_of_current_test(), + color.test, + fullname_of_current_test(), + color.none + ); + } + } + } + + auto on(const events::EventTestSkip& test_skip) noexcept -> void + { + on(events::EventTestBegin{.name = test_skip.name}); + current_test_result_->status = test_result_type::Status::SKIPPED_FILTERED; + on(events::EventTestEnd{.name = test_skip.name}); + } + + auto on(const events::EventTestEnd& test_end) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ != nullptr); + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_->name == test_end.name); + + const auto time_count = current_test_result_->time.count(); + if (current_test_result_->status == test_result_type::Status::PENDING) + [[likely]] + { + // the current test is considered SKIPPED only if it does not have any assertions and has no children. + if (current_test_result_->total_assertions_failed == 0 and current_test_result_->total_assertions_passed == 0) + { + if (current_test_result_->children.empty()) + { + current_test_result_->status = test_result_type::Status::SKIPPED_NO_ASSERTION; + } + else + { + current_test_result_->status = + std::ranges::all_of( + current_test_result_->children, + [](const auto& child_test) noexcept + { + return child_test.total_assertions_failed == 0; + } + ) + ? test_result_type::Status::PASSED + : test_result_type::Status::FAILED; + } + } + else + { + current_test_result_->status = current_test_result_->total_assertions_failed == 0 + ? test_result_type::Status::PASSED + : test_result_type::Status::FAILED; + } + } + + if (executor().config_check_report_level()) + { + const auto prefix = executor().config_output_prefix(); + const auto& color = executor().config_output_color(); + + if (const auto status = current_test_result_->status; + status == test_result_type::Status::PASSED or status == test_result_type::Status::FAILED) + [[likely]] + { + std::format_to( + std::back_inserter(suite().report_string), + "{:<{}}{}{}{} after {} milliseconds.\n", + prefix, + ident_size_of_current_test(), + status == test_result_type::Status::PASSED ? color.pass : color.failure, + status == test_result_type::Status::PASSED ? "PASSED" : "FAILED", + color.none, + time_count + ); + } + else if (status == test_result_type::Status::SKIPPED_NO_ASSERTION or status == test_result_type::Status::SKIPPED_FILTERED) + [[unlikely]] + { + std::format_to( + std::back_inserter(suite().report_string), + "{:<{}}{}SKIPPED{} --- [{}] \n", + prefix, + ident_size_of_current_test(), + color.skip, + color.none, + status == test_result_type::Status::SKIPPED_NO_ASSERTION + ? "No Assertion(s) Found" + : "FILTERED" + ); + } + else if (status == test_result_type::Status::INTERRUPTED or status == test_result_type::Status::TERMINATED) + [[unlikely]] + { + std::format_to( + std::back_inserter(suite().report_string), + "{:<{}}{}{}{}\n", + prefix, + ident_size_of_current_test(), + color.fatal, + status == test_result_type::Status::INTERRUPTED + ? "INTERRUPTED" + : "TERMINATED", + color.none + ); + } + else { std::unreachable(); } + } + + // reset to parent test + current_test_result_ = current_test_result_->parent; + } + + // ========================================= + // ASSERTION + // ========================================= + + template + auto on(const events::EventAssertion& assertion, internal_tag) noexcept(false) -> bool + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ != nullptr); + + if (static_cast(assertion.expression)) + [[likely]] + { + this->on(assertion.pass()); + return true; + } + + this->on(assertion.fail()); + return false; + } + + template + auto on(const events::EventAssertionPass& assertion_pass) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ != nullptr); + + if (executor().config_check_report_level()) + { + // @see: Operand::prefer_no_type_name + constexpr auto prefer_no_type_name = requires { typename Expression::prefer_no_type_name; }; + + const auto prefix = executor().config_output_prefix(); + const auto& color = executor().config_output_color(); + + std::format_to( + std::back_inserter(suite().report_string), + "{:<{}}[{}:{}] {}[{}]{} - {}PASSED{} \n", + prefix, + ident_size_of_current_test(), + assertion_pass.location.file_name(), + assertion_pass.location.line(), + color.expression, + meta::to_string(assertion_pass.expression), + color.none, + color.pass, + color.none + ); + } + + current_test_result_->total_assertions_passed += 1; + } + + template + auto on(const events::EventAssertionFail& assertion_fail) noexcept(false) -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ != nullptr); + + GAL_PROMETHEUS_ERROR_BREAKPOINT_IF(executor().config_check_break_point(), "EventAssertionFail"); + + if (executor().config_check_report_level()) + { + // @see: Operand::prefer_no_type_name + constexpr auto prefer_no_type_name = requires { typename Expression::prefer_no_type_name; }; + + const auto prefix = executor().config_output_prefix(); + const auto& color = executor().config_output_color(); + + std::format_to( + std::back_inserter(suite().report_string), + "{:<{}}[{}:{}] {}[{}]{} - {}FAILED{} \n", + prefix, + ident_size_of_current_test(), + assertion_fail.location.file_name(), + assertion_fail.location.line(), + color.expression, + meta::to_string(assertion_fail.expression), + color.none, + color.failure, + color.none + ); + } + + current_test_result_->total_assertions_failed += 1; + + check_total_failures(); + } + + [[noreturn]] auto on(const events::EventAssertionFatal& assertion_fatal, internal_tag) noexcept(false) -> void + { + GAL_PROMETHEUS_ERROR_BREAKPOINT_IF(executor().config_check_break_point(), "EventAssertionFatal"); + + if (executor().config_check_report_level()) + { + const auto prefix = executor().config_output_prefix(); + const auto& color = executor().config_output_color(); + + std::format_to( + std::back_inserter(suite().report_string), + "{:<{}}^^^ {}FATAL ERROR! END TEST!{}\n", + prefix, + ident_size_of_current_test() + + ( + // '[' + 1 + + // file_name + std::string_view::traits_type::length(assertion_fatal.location.file_name())) + + // ':' + 1 + + // line + [](Line line) + { + Line result = 0; + while (line) + { + result += 1; + line /= 10; + } + return result; + }(assertion_fatal.location.line()) + + // "] [" + 3, + color.fatal, + color.none + ); + } + + check_total_failures(); + + throw EndThisTest{}; // NOLINT(hicpp-exception-baseclass) + } + + // ========================================= + // UNEXPECTED + // ========================================= + + auto on(const events::EventUnexpected& unexpected) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ != nullptr); + + const auto& color = executor().config_output_color(); + + std::format_to( + std::back_inserter(suite().name), + "Unhandled exception threw from {}: {}{}{}\n", + fullname_of_current_test(), + color.failure, + unexpected.what(), + color.none + ); + } + + // ========================================= + // LOG + // ========================================= + + template + auto on(const events::EventLog& log, internal_tag) noexcept -> void + { + if (log.message != "\n") + [[likely]] + { + // pop '\n' + suite().report_string.pop_back(); + } + + const auto& color = executor().config_output_color(); + + suite().report_string.append(color.message); + #if __cpp_lib_containers_ranges >= 202202L + suite().report_string.append_range(log.message); + #else + suite().report_string.insert(suite().report_string.end(), std::ranges::begin(log.message), std::ranges::end(log.message)); + #endif + suite().report_string.append(color.none); + + // push '\n' + suite().report_string.push_back('\n'); + } + + Worker() noexcept + : + suite_{nullptr}, + current_test_result_{nullptr} {} + + public: + [[nodiscard]] static auto instance() noexcept -> Worker& + { + // one worker per thread + thread_local Worker worker{}; + return worker; + } + + // ========================================= + // TEST + // ========================================= + + template + auto on(const events::EventTest& test) noexcept(false) -> void + { + this->on(test, internal_tag{}); + } + + // ========================================= + // ASSERTION + // ========================================= + + template + auto on(const events::EventAssertion& assertion) noexcept(false) -> bool + { + return this->on(assertion, internal_tag{}); + } + + auto on(const events::EventAssertionFatal& assertion_fatal) noexcept(false) -> void + { + on(assertion_fatal, internal_tag{}); + } + + // ========================================= + // LOG + // ========================================= + + template + auto on(const events::EventLog& log) noexcept -> void + { + this->on(log, internal_tag{}); + } + }; + + inline auto Executor::worker_work(const events::EventSuite& suite, suite_result_type& suite_result) noexcept -> bool + { + const auto error = [this]() noexcept -> bool + { + std::scoped_lock lock{mutex_reporting_}; + return is_executor_fatal_error(); + }(); + if (not error) + { + auto& worker = Worker::instance(); + worker.run(suite, suite_result); + return true; + } + + return false; + } + + inline auto Executor::worker_thread_func(const std::size_t thread_index, const std::size_t step) noexcept -> void + { + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(suites_.size() == suite_results_.size()); + + for (const auto bucket_count = suites_.size() / step; + const auto index_in_bucket: std::views::iota(static_cast(0), bucket_count)) + { + const auto index = thread_index + index_in_bucket * step; + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(index < suites_.size()); + + // for print + worker_job_tracer_[thread_index] = index; + + const auto& suite = suites_[index]; + auto& suite_result = suite_results_[index]; + + if (const auto result = worker_work(suite, suite_result); + not result) + { + break; + } + } + + // for print + worker_job_tracer_[thread_index] = worker_job_done; + } +} diff --git a/src/unit_test/operands.hpp b/src/unit_test/operands.hpp new file mode 100644 index 00000000..f38e1f69 --- /dev/null +++ b/src/unit_test/operands.hpp @@ -0,0 +1,984 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +namespace gal::prometheus::unit_test::operands +{ + class Operand + { + public: + // magic + using prefer_no_type_name = int; + }; + + template + constexpr auto is_operand_v = std::is_base_of_v; + template + concept operand_t = is_operand_v; + + // ========================================= + // VALUE / REFERENCE + // ========================================= + + template + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandValue final : public Operand + { + public: + using value_type = T; + + private: + value_type value_; + + public: + constexpr explicit(false) OperandValue(const value_type value) // + noexcept(std::is_nothrow_copy_constructible_v) // + requires(std::is_trivially_copy_constructible_v) + : value_{value} {} + + constexpr explicit(false) OperandValue(const value_type& value) // + noexcept(std::is_nothrow_copy_constructible_v) // + requires( + not std::is_trivially_copy_constructible_v and + std::is_copy_constructible_v) + : value_{value} {} + + constexpr explicit(false) OperandValue(value_type&& value) noexcept(std::is_nothrow_move_constructible_v) // + requires( + not std::is_trivially_copy_constructible_v and + std::is_move_constructible_v) + : value_{std::move(value)} {} + + template + requires(std::is_trivially_constructible_v) + constexpr explicit(false) OperandValue(const value_type value) // + noexcept(std::is_nothrow_constructible_v) // + : value_{value} {} + + template + requires( + not std::is_trivially_constructible_v and + std::is_constructible_v) + constexpr explicit(false) OperandValue(const U& value) // + noexcept(std::is_nothrow_constructible_v) // + : value_{value} {} + + template + requires( + not std::is_trivially_constructible_v and + std::is_constructible_v) + constexpr explicit(false) OperandValue(U&& value) // + noexcept(std::is_nothrow_constructible_v) + : value_{std::forward(value)} {} + + template + requires((sizeof...(Args) != 1) and std::is_constructible_v) + constexpr explicit OperandValue(Args&&... args) // + noexcept(std::is_nothrow_constructible_v) + : value_{std::forward(args)...} {} + + [[nodiscard]] constexpr auto value() noexcept -> value_type& { return value_; } + + [[nodiscard]] constexpr auto value() const noexcept -> const value_type& { return value_; } + + template StringType> + requires std::ranges::contiguous_range + constexpr auto to_string(StringType& out) const noexcept -> void + { + return meta::to_string(value_, out); + } + }; + + // ReSharper disable CppInconsistentNaming + template + OperandValue(T) -> OperandValue; + // ReSharper restore CppInconsistentNaming + + template + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandValueRef final : public Operand + { + public: + using value_type = T; + + private: + std::reference_wrapper ref_; + + public: + constexpr explicit(false) OperandValueRef(value_type& ref) noexcept + : ref_{ref} {} + + [[nodiscard]] constexpr auto value() noexcept -> value_type& { return ref_.get(); } + + [[nodiscard]] constexpr auto value() const noexcept -> const value_type& { return ref_.get(); } + + template StringType> + requires std::ranges::contiguous_range + constexpr auto to_string(StringType& out) const noexcept -> void + { + return meta::to_string(ref_.get(), out); + } + }; + + // ReSharper disable CppInconsistentNaming + template + OperandValueRef(T&) -> OperandValueRef; + // propagate const + template + OperandValueRef(const T&) -> OperandValueRef; + // ReSharper restore CppInconsistentNaming + + template + struct is_operand_value : std::false_type {}; + + template + struct is_operand_value> : std::true_type {}; + + template + struct is_operand_value> : std::true_type {}; + + template + constexpr auto is_operand_value_v = is_operand_value::value; + template + concept operand_value_t = is_operand_value_v; + + // ========================================= + // LITERAL + // ========================================= + + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandLiteral : public Operand {}; + + template + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandLiteralCharacter final : public OperandLiteral + { + public: + using value_type = char; + + constexpr static auto value = Value; + + template StringType> + requires std::ranges::contiguous_range + constexpr auto to_string(StringType& out) const noexcept -> void + { + return meta::to_string(value, out); + } + }; + + template + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandLiteralIntegral final : public OperandLiteral + { + public: + using value_type = std::remove_cvref_t; + + constexpr static value_type value = Value; + + [[nodiscard]] constexpr auto operator-() const noexcept -> OperandLiteralIntegral<-static_cast>(value)> + { + return {}; + } + + template StringType> + requires std::ranges::contiguous_range + constexpr auto to_string(StringType& out) const noexcept -> void + { + return meta::to_string(value, out); + } + }; + + template + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandLiteralFloatingPoint final : public OperandLiteral + { + public: + using value_type = std::remove_cvref_t; + + constexpr static value_type value = Value; + constexpr static std::size_t denominator_size = DenominatorSize; + constexpr static value_type epsilon = [](std::size_t n) noexcept -> value_type + { + auto epsilon = static_cast(1); + while (n--) { epsilon /= static_cast(10); } + return epsilon; + }(DenominatorSize); + + [[nodiscard]] constexpr auto operator-() const noexcept -> OperandLiteralFloatingPoint<-value, DenominatorSize> { return {}; } + + template StringType> + requires std::ranges::contiguous_range + constexpr auto to_string(StringType& out) const noexcept -> void + { + return std::format_to(std::back_inserter(out), "{:.{}g}", DenominatorSize, value); + } + }; + + template + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandLiteralAuto final : public OperandLiteral + { + public: + constexpr static auto char_list = functional::char_list; + + template + struct rep; + + template + struct rep> + { + using type = OperandLiteralCharacter()>; + }; + + template + struct rep> + { + using type = OperandLiteralIntegral::value_type>()>; + }; + + template + struct rep> + { + using type = OperandLiteralFloatingPoint< + char_list.template to_floating_point::value_type>, + char_list.denominator_length() + >; + }; + + template + struct rep + { + using type = std::conditional_t< + std::is_same_v, + OperandLiteralCharacter()>, + OperandLiteralIntegral()> + >; + }; + + template + struct rep + { + using type = OperandLiteralFloatingPoint(), char_list.denominator_length()>; + }; + + template + using rebind = typename rep>::type; + }; + + // ======================== + // literal + + template + constexpr auto is_operand_literal_v = std::is_base_of_v; + template + concept operand_literal_t = is_operand_literal_v; + + // ======================== + // literal character + + template + struct is_operand_literal_character : std::false_type {}; + + template + struct is_operand_literal_character> : std::true_type {}; + + template + constexpr auto is_operand_literal_character_v = is_operand_literal_character::value; + template + concept operand_literal_character_t = is_operand_literal_character_v; + + // ======================== + // literal integral + + template + struct is_operand_literal_integral : std::false_type {}; + + template + struct is_operand_literal_integral> : std::true_type {}; + + template + constexpr auto is_operand_literal_integral_v = is_operand_literal_integral::value; + template + concept operand_literal_integral_t = is_operand_literal_integral_v; + + // ======================== + // literal floating point + + template + struct is_operand_literal_floating_point : std::false_type {}; + + template + struct is_operand_literal_floating_point> : std::true_type {}; + + template + constexpr auto is_operand_literal_floating_point_v = is_operand_literal_floating_point::value; + template + concept operand_literal_floating_point_t = is_operand_literal_floating_point_v; + + // ======================== + // literal auto-deducing + + template + struct is_operand_literal_auto : std::false_type {}; + + template + struct is_operand_literal_auto> : std::true_type {}; + + template + constexpr auto is_operand_literal_auto_v = is_operand_literal_auto::value; + template + concept operand_literal_auto_t = is_operand_literal_auto_v; + + // ========================================= + // IDENTITY (message) + // ========================================= + + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandIdentityBoolean final : public Operand + { + public: + // explicit unique type + struct value_type + { + std::string_view string; + }; + + private: + value_type value_; + bool result_; + + public: + constexpr OperandIdentityBoolean(const value_type value, const bool result) noexcept + : value_{value}, + result_{result} {} + + [[nodiscard]] constexpr explicit operator bool() const noexcept { return result_; } + + template StringType> + requires std::ranges::contiguous_range + constexpr auto to_string(StringType& out) const noexcept -> void + { + std::format_to(std::back_inserter(out), "{}", value_.string); + } + }; + + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandIdentityString final : public Operand + { + public: + using identity_type = std::string_view; + + // explicit unique type + struct value_type + { + identity_type string; + }; + + private: + value_type value_; + + public: + constexpr explicit OperandIdentityString(const value_type value) noexcept + : value_{value} {} + + // error : member access into incomplete type 'const OperandIdentityString' + // identity.value_.string == std::forward(string); + // ^ + #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) + + [[nodiscard]] constexpr auto identity() const noexcept -> identity_type + { + return value_.string; + } + + #else + + // recursively required by substitution of ‘template constexpr bool operands::operator==(const OperandIdentityString&, const T&) [with T = operands::OperandIdentityString]’ + // error: satisfaction of atomic constraint ‘requires{identity->value_.string == string;} [with StringType = StringType]’ depends on itself + // requires requires + // ^~~~~~ + // { + // ~ + // identity.value_.string == string; + // ~~~~~~~~~~~~~~~~~~~~ + // } + // ~ + #if defined(GAL_PROMETHEUS_COMPILER_GNU) + + template + requires (not std::is_same_v) + [[nodiscard]] friend constexpr auto operator==(const OperandIdentityString& identity, const StringType& string) noexcept -> bool // + { + if constexpr ( + requires(const std::string_view lhs, const StringType& rhs) + { + {lhs == rhs} -> std::convertible_to; + } + ) + { + return identity.value_.string == string; + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE("In one of your `\"xxx\"_b == xxx` expressions, xxx does not support `operator==(std::string_view)`."); + } + } + + template + requires (not std::is_same_v) + [[nodiscard]] friend constexpr auto operator==(const StringType& string, const OperandIdentityString& identity) noexcept -> bool // + { + if constexpr ( + requires(const StringType& lhs, const std::string_view rhs) + { + {lhs == rhs} -> std::convertible_to; + } + ) + { + return string == identity.value_.string; + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE("In one of your `xxx == \"xxx\"_b` expressions, xxx does not support `operator==(std::string_view)`."); + } + } + + #else + + template + requires requires(const std::string_view lhs, const StringType& rhs) + { + { lhs == rhs } -> std::convertible_to; + } + [[nodiscard]] friend constexpr auto operator==(const OperandIdentityString& identity, const StringType& string) noexcept -> bool // + { + return identity.value_.string == string; + } + + template + requires requires(const StringType& lhs, const std::string_view rhs) + { + { lhs == rhs } -> std::convertible_to; + } + [[nodiscard]] friend constexpr auto operator==(const StringType& string, const OperandIdentityString& identity) noexcept -> bool // + { + return string == identity.value_.string; + } + + #endif + + #endif + + template StringType> + requires std::ranges::contiguous_range + constexpr auto to_string(StringType& out) const noexcept -> void + { + std::format_to(std::back_inserter(out), "\"{}\"", value_.string); + } + }; + + #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) + template + [[nodiscard]] constexpr auto operator==(const OperandIdentityString& identity, const StringType& string) noexcept -> bool // + requires requires + { + identity.identity() == string; + } + { + return identity.identity() == string; + } + + template + [[nodiscard]] constexpr auto operator==(const StringType& string, const OperandIdentityString& identity) noexcept -> bool // + requires requires + { + string == identity.identity(); + } + { + return string == identity.identity(); + } + #endif + + // ======================== + // identity boolean + + template + struct is_operand_identity_boolean : std::false_type {}; + + template<> + struct is_operand_identity_boolean : std::true_type {}; + + template + constexpr auto is_operand_identity_boolean_v = is_operand_identity_boolean::value; + + template + concept operand_identity_boolean_t = is_operand_identity_boolean_v; + + // ======================== + // identity string + + template + struct is_operand_identity_string : std::false_type {}; + + template<> + struct is_operand_identity_string : std::true_type {}; + + template + constexpr auto is_operand_identity_string_v = is_operand_identity_string::value; + + template + concept operand_identity_string_t = is_operand_identity_string_v; + + // ========================================= + // EXPRESSION + // ========================================= + + enum class ExpressionCategory : std::uint8_t + { + EQUAL, + APPROX, + NOT_EQUAL, + NOT_APPROX, + GREATER_THAN, + GREATER_EQUAL, + LESS_THAN, + LESS_EQUAL, + LOGICAL_AND, + LOGICAL_OR, + }; + + struct no_epsilon {}; + + template + requires(not(is_operand_literal_auto_v and is_operand_literal_auto_v)) + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandExpression final : public Operand + { + public: + constexpr static auto category = Category; + + // If one side of the expression is OperandLiterAuto and the other side is not OperandLiteralXxx then a compile error should be raised. + static_assert((not is_operand_literal_auto_v) or (is_operand_literal_auto_v == is_operand_literal_v)); + static_assert((not is_operand_literal_auto_v) or (is_operand_literal_auto_v == is_operand_literal_v)); + + // using left_type = std::conditional_t, typename Left::template rebind, Left>; + // using right_type = std::conditional_t, typename Right::template rebind, Right>; + using left_type = Left; + using right_type = Right; + using epsilon_type = Epsilon; + + private: + left_type left_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + right_type right_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + epsilon_type epsilon_; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + bool result_; + + [[nodiscard]] constexpr auto do_check() const noexcept -> bool + { + // fixme: + // Explicitly specifying the parameter type would help us get better compiler diagnostics, + // but it seems that the compiler refuses to accept this code :( + + // const auto do_compare = []( + // const L& left, + // const R& right, + // const E& epsilon + // ) noexcept -> bool + const auto do_compare = []( + const auto& left, + const auto& right, + const auto& epsilon + ) noexcept -> bool + { + if constexpr (category == ExpressionCategory::EQUAL) + { + using std::operator==; + using std::operator!=; + if constexpr (requires { left == right; }) + { + return left == right; + } + else if constexpr (requires { right == left; }) + { + return right == left; + } + else if constexpr (requires { left != right; }) + { + return not(left != right); + } + else if constexpr (requires { right != left; }) + { + return not(right != left); + } + else if constexpr (requires { { left.compare(right) } -> std::same_as; }) + { + return left.compare(right); + } + else if constexpr (requires { left.compare(right); }) + { + return left.compare(right) == 0; + } + else if constexpr (requires { { right.compare(left) } -> std::same_as; }) + { + return right.compare(left); + } + else if constexpr (requires { right.compare(left); }) + { + return right.compare(left) == 0; + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE("Not comparable!"); + } + } + else if constexpr (category == ExpressionCategory::APPROX) + { + using std::operator-; + using std::operator<; + return math::abs(left - right) < epsilon; // NOLINT(clang-diagnostic-implicit-int-float-conversion) + } + else if constexpr (category == ExpressionCategory::NOT_EQUAL) + { + using std::operator!=; + using std::operator==; + if constexpr (requires { left != right; }) + { + return left != right; + } + else if constexpr (requires { right != left; }) + { + return right != left; + } + else if constexpr (requires { left == right; }) + { + return not(left == right); + } + else if constexpr (requires { right == left; }) + { + return not(right == left); + } + else if constexpr (requires { { left.compare(right) } -> std::same_as; }) + { + return not left.compare(right); + } + else if constexpr (requires { left.compare(right); }) + { + return left.compare(right) != 0; + } + else if constexpr (requires { { right.compare(left) } -> std::same_as; }) + { + return not right.compare(left); + } + else if constexpr (requires { right.compare(left); }) + { + return right.compare(left) != 0; + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE("Not comparable!"); + } + } + else if constexpr (category == ExpressionCategory::NOT_APPROX) + { + using std::operator-; + using std::operator<; + return epsilon < math::abs(left - right); + } + else if constexpr (category == ExpressionCategory::GREATER_THAN) + { + using std::operator>; + return left > right; + } + else if constexpr (category == ExpressionCategory::GREATER_EQUAL) + { + using std::operator>=; + return left >= right; + } + else if constexpr (category == ExpressionCategory::LESS_THAN) + { + using std::operator<; + return left < right; + } + else if constexpr (category == ExpressionCategory::LESS_EQUAL) + { + using std::operator<=; + return left <= right; + } + else if constexpr (category == ExpressionCategory::LOGICAL_AND) + { + return static_cast(left) and static_cast(right); + } + else if constexpr (category == ExpressionCategory::LOGICAL_OR) + { + return static_cast(left) or static_cast(right); + } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + }; + + const auto get_value = []([[maybe_unused]] const T& target) noexcept -> decltype(auto) // + { + [[maybe_unused]] constexpr auto not_member_function_pointer = [](P) constexpr noexcept + { + return not std::is_member_function_pointer_v>; + }; + + if constexpr (requires { target.value(); }) + { + // member function + return target.value(); + } + else if constexpr (requires { not_member_function_pointer(T::value); }) + { + // static variable + return T::value; + } + else + { + return target; + } + }; + + return do_compare(get_value(left_), get_value(right_), get_value(epsilon_)); + } + + public: + template + constexpr OperandExpression( + L&& left, + R&& right, + E&& epsilon + ) noexcept + : left_{std::forward(left)}, + right_{std::forward(right)}, + epsilon_{std::forward(epsilon)}, + result_{do_check()} {} + + template + constexpr OperandExpression( + L&& left, + R&& right + ) noexcept + : left_{std::forward(left)}, + right_{std::forward(right)}, + epsilon_{}, + result_{do_check()} {} + + [[nodiscard]] constexpr explicit operator bool() const noexcept { return result_; } + + template StringType> + requires std::ranges::contiguous_range + constexpr auto to_string(StringType& out) const noexcept -> void + { + constexpr auto left_prefer_no_type_name = requires { typename left_type::prefer_no_type_name; }; + constexpr auto right_prefer_no_type_name = requires { typename right_type::prefer_no_type_name; }; + constexpr auto epsilon_prefer_no_type_name = requires { typename right_type::prefer_no_type_name; }; + + if constexpr (category == ExpressionCategory::EQUAL) + { + std::format_to( + std::back_inserter(out), + "{} == {}", + meta::to_string(left_), + meta::to_string(right_) + ); + } + else if constexpr (category == ExpressionCategory::APPROX) + { + std::format_to( + std::back_inserter(out), + "{} ≈≈ {} (+/- {})", + meta::to_string(left_), + meta::to_string(right_), + meta::to_string(epsilon_) + ); + } + else if constexpr (category == ExpressionCategory::NOT_EQUAL) + { + std::format_to( + std::back_inserter(out), + "{} != {}", + meta::to_string(left_), + meta::to_string(right_) + ); + } + else if constexpr (category == ExpressionCategory::NOT_APPROX) + { + std::format_to( + std::back_inserter(out), + "{} !≈ {} (+/- {})", + meta::to_string(left_), + meta::to_string(right_), + meta::to_string(epsilon_) + ); + } + else if constexpr (category == ExpressionCategory::GREATER_THAN) + { + std::format_to( + std::back_inserter(out), + "{} > {}", + meta::to_string(left_), + meta::to_string(right_) + ); + } + else if constexpr (category == ExpressionCategory::GREATER_EQUAL) + { + std::format_to( + std::back_inserter(out), + "{} >= {}", + meta::to_string(left_), + meta::to_string(right_) + ); + } + else if constexpr (category == ExpressionCategory::LESS_THAN) + { + std::format_to( + std::back_inserter(out), + "{} < {}", + meta::to_string(left_), + meta::to_string(right_) + ); + } + else if constexpr (category == ExpressionCategory::LESS_EQUAL) + { + std::format_to( + std::back_inserter(out), + "{} <= {}", + meta::to_string(left_), + meta::to_string(right_) + ); + } + else if constexpr (category == ExpressionCategory::LOGICAL_AND) + { + std::format_to( + std::back_inserter(out), + "{} and {}", + meta::to_string(left_), + meta::to_string(right_) + ); + } + else if constexpr (category == ExpressionCategory::LOGICAL_OR) + { + std::format_to( + std::back_inserter(out), + "{} or {}", + meta::to_string(left_), + meta::to_string(right_) + ); + } + else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } + } + }; + + template + struct is_operand_expression : std::false_type {}; + + template + struct is_operand_expression> : std::true_type {}; + + template + constexpr auto is_operand_expression_v = is_operand_expression::value; + template + concept operand_expression_t = is_operand_expression_v; + + // ========================================= + // EXCEPTION + // ========================================= + + template + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandThrow final : public Operand + { + public: + using exception_type = Exception; + + private: + bool thrown_; + bool caught_; + + public: + template + constexpr explicit OperandThrow(Invocable&& invocable) noexcept + : thrown_{false}, + caught_{false} + { + if constexpr (std::is_same_v) + { + try { std::invoke(std::forward(invocable)); } + catch (...) + { + thrown_ = true; + caught_ = true; + } + } + else + { + try { std::invoke(std::forward(invocable)); } + catch ([[maybe_unused]] const exception_type& exception) + { + thrown_ = true; + caught_ = true; + } + catch (...) + { + thrown_ = true; + caught_ = false; + } + } + } + + [[nodiscard]] constexpr auto thrown() const noexcept -> bool { return thrown_; } + [[nodiscard]] constexpr auto caught() const noexcept -> bool { return caught_; } + + [[nodiscard]] constexpr explicit operator bool() const noexcept { return caught(); } + + template StringType> + requires std::ranges::contiguous_range + constexpr auto to_string(StringType& out) const noexcept -> void + { + std::format_to( + std::back_inserter(out), + "throws<{}> -- [{}]", + meta::name_of(), + (not thrown()) + ? "not thrown" + : // + (not caught()) + ? "thrown but not caught" + : // + "caught" + ); + } + }; + + class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandNoThrow final : public Operand + { + public: + using exception_type = void; + + private: + bool thrown_; + + public: + template + constexpr explicit OperandNoThrow(Invocable&& invocable) noexcept + : thrown_{false} + { + try { std::invoke(std::forward(invocable)); } + catch (...) { thrown_ = true; } + } + + [[nodiscard]] constexpr explicit operator bool() const noexcept { return not thrown_; } + + template StringType> + requires std::ranges::contiguous_range + constexpr auto to_string(StringType& out) const noexcept -> void + { + std::format_to(std::back_inserter(out), "nothrow - {:s}", not thrown_); + } + }; +} diff --git a/src/unit_test/unit_test.hpp b/src/unit_test/unit_test.hpp new file mode 100644 index 00000000..51f7f15d --- /dev/null +++ b/src/unit_test/unit_test.hpp @@ -0,0 +1,349 @@ +// This file is part of prometheus +// Copyright (C) 2022-2025 Life4gal +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +// inspired by boost-ext/ut(https://github.com/boost-ext/ut)(http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#include +#include +#include +#include +#include + +namespace gal::prometheus::unit_test +{ + // ========================================= + // OPERANDS + // ========================================= + + template + [[nodiscard]] constexpr auto value(T&& v) noexcept -> auto + { + return operands::OperandValue{std::forward(v)}; + } + + template + [[nodiscard]] constexpr auto ref(T& v) noexcept -> auto + { + return operands::OperandValueRef{v}; + } + + template + [[nodiscard]] constexpr auto ref(const T& v) noexcept -> auto + { + return operands::OperandValueRef{v}; + } + + template + [[nodiscard]] constexpr auto throws(const InvocableType& invocable) noexcept -> operands::OperandThrow + { + return {invocable}; + } + + template + [[nodiscard]] constexpr auto nothrow(const InvocableType& invocable) noexcept -> operands::OperandNoThrow + { + return {invocable}; + } + + // ========================================= + // DISPATCHER + // ========================================= + + constexpr dispatcher::DispatcherThat that{}; + + constexpr dispatcher::DispatcherExpect expect{}; + // The assertion must succeed, otherwise the assertion(s) (and nested test(s)) followed (this test) will be skipped + constexpr dispatcher::ExpectResult::fatal fatal{}; + + // ========================================= + // CONFIG + // ========================================= + + inline auto set_config(config_type&& config) noexcept -> void + { + return executor::Executor::instance().set_config(std::move(config)); + } + + // ========================================= + // SUITE & TEST + // ========================================= + + template + using suite = dispatcher::DispatcherSuite; + + using test = dispatcher::DispatcherTest; + + // ========================================= + // OPERATORS + // ========================================= + + inline namespace operators + { + namespace detail + { + template + concept dispatchable_t = + not( + dispatcher::is_dispatched_expression_v or + dispatcher::is_dispatched_expression_v + ); + + template + // ReSharper disable once CppFunctionIsNotImplemented + constexpr auto is_valid_dispatched_expression(DispatchedExpression&& expression) noexcept -> void // + requires requires + { + static_cast(expression.expression); + }; + } // namespace detail + + // a == b + template + [[nodiscard]] constexpr auto operator==(Lhs&& lhs, Rhs&& rhs) noexcept -> decltype(auto) // + requires detail::dispatchable_t and + requires { detail::is_valid_dispatched_expression(that % std::forward(lhs) == std::forward(rhs)); } // + { + return that % std::forward(lhs) == std::forward(rhs); + } + + // a != b + template + [[nodiscard]] constexpr auto operator!=(Lhs&& lhs, Rhs&& rhs) noexcept -> decltype(auto) // + requires detail::dispatchable_t and + requires { detail::is_valid_dispatched_expression(that % std::forward(lhs) != std::forward(rhs)); } // + { + return that % std::forward(lhs) != std::forward(rhs); + } + + // a > b + template + [[nodiscard]] constexpr auto operator>(Lhs&& lhs, Rhs&& rhs) noexcept -> decltype(auto) // + requires detail::dispatchable_t and + requires { detail::is_valid_dispatched_expression(that % std::forward(lhs) > std::forward(rhs)); } // + { + return that % std::forward(lhs) > std::forward(rhs); + } + + // a >= b + template + [[nodiscard]] constexpr auto operator>=(Lhs&& lhs, Rhs&& rhs) noexcept -> decltype(auto) // + requires detail::dispatchable_t and + requires { detail::is_valid_dispatched_expression(that % std::forward(lhs) >= std::forward(rhs)); } // + { + return that % std::forward(lhs) >= std::forward(rhs); + } + + // a < b + template + [[nodiscard]] constexpr auto operator<(Lhs&& lhs, Rhs&& rhs) noexcept -> decltype(auto) // + requires detail::dispatchable_t and + requires { detail::is_valid_dispatched_expression(that % std::forward(lhs) < std::forward(rhs)); } // + { + return that % std::forward(lhs) < std::forward(rhs); + } + + // a <= b + template + [[nodiscard]] constexpr auto operator<=(Lhs&& lhs, Rhs&& rhs) noexcept -> decltype(auto) // + requires detail::dispatchable_t and + requires { detail::is_valid_dispatched_expression(that % std::forward(lhs) <= std::forward(rhs)); } // + { + return that % std::forward(lhs) <= std::forward(rhs); + } + + // a and b + template + [[nodiscard]] constexpr auto operator and(Lhs&& lhs, Rhs&& rhs) noexcept -> decltype(auto) // + requires detail::dispatchable_t and + requires { detail::is_valid_dispatched_expression(that % std::forward(lhs) and std::forward(rhs)); } // + { + return that % std::forward(lhs) and std::forward(rhs); + } + + // a or b + template + [[nodiscard]] constexpr auto operator or(Lhs&& lhs, Rhs&& rhs) noexcept -> decltype(auto) // + requires detail::dispatchable_t and + requires { detail::is_valid_dispatched_expression(that % std::forward(lhs) or std::forward(rhs)); } // + { + return that % std::forward(lhs) or std::forward(rhs); + } + } // namespace operators + + // ========================================= + // LITERALS + // ========================================= + + inline namespace literals + { + template + [[nodiscard]] constexpr auto operator""_test() noexcept -> dispatcher::DispatcherTestLiteral // + { + return dispatcher::DispatcherTestLiteral{}; + } + + template + [[nodiscard]] constexpr auto operator""_auto() noexcept -> operands::OperandLiteralAuto // + { + return {}; + } + + template + [[nodiscard]] constexpr auto operator""_c() noexcept -> operands::OperandLiteralCharacter // + { + return {}; + } + + template + requires requires { functional::char_list.template to_integral(); } + [[nodiscard]] constexpr auto operator""_i() noexcept + -> operands::OperandLiteralIntegral.template to_integral()> // + { + return {}; + } + + template + requires requires { functional::char_list.template to_integral(); } + [[nodiscard]] constexpr auto operator""_u() noexcept + -> operands::OperandLiteralIntegral.template to_integral()> // + { + return {}; + } + + template + requires requires { functional::char_list.template to_integral(); } + [[nodiscard]] constexpr auto operator""_l() noexcept + -> operands::OperandLiteralIntegral.template to_integral()> // + { + return {}; + } + + template + requires requires { functional::char_list.template to_integral(); } + [[nodiscard]] constexpr auto operator""_ul() noexcept + -> operands::OperandLiteralIntegral.template to_integral()> // + { + return {}; + } + + template + requires requires { functional::char_list.template to_integral(); } + [[nodiscard]] constexpr auto operator""_ll() noexcept + -> operands::OperandLiteralIntegral.template to_integral()> // + { + return {}; + } + + template + requires requires { functional::char_list.template to_integral(); } + [[nodiscard]] constexpr auto operator""_ull() noexcept + -> operands::OperandLiteralIntegral.template to_integral()> // + { + return {}; + } + + template + requires requires { functional::char_list.template to_integral(); } + [[nodiscard]] constexpr auto operator""_i8() noexcept + -> operands::OperandLiteralIntegral.template to_integral()> // + { + return {}; + } + + template + requires requires { functional::char_list.template to_integral(); } + [[nodiscard]] constexpr auto operator""_u8() noexcept + -> operands::OperandLiteralIntegral.template to_integral()> // + { + return {}; + } + + template + requires requires { functional::char_list.template to_integral(); } + [[nodiscard]] constexpr auto operator""_i16() noexcept + -> operands::OperandLiteralIntegral.template to_integral()> // + { + return {}; + } + + template + requires requires { functional::char_list.template to_integral(); } + [[nodiscard]] constexpr auto operator""_u16() noexcept + -> operands::OperandLiteralIntegral.template to_integral()> // + { + return {}; + } + + template + requires requires { functional::char_list.template to_integral(); } + [[nodiscard]] constexpr auto operator""_i32() noexcept + -> operands::OperandLiteralIntegral.template to_integral()> // + { + return {}; + } + + template + requires requires { functional::char_list.template to_integral(); } + [[nodiscard]] constexpr auto operator""_u32() noexcept + -> operands::OperandLiteralIntegral.template to_integral()> // + { + return {}; + } + + template + requires requires { functional::char_list.template to_integral(); } + [[nodiscard]] constexpr auto operator""_i64() noexcept + -> operands::OperandLiteralIntegral.template to_integral()> // + { + return {}; + } + + template + requires requires { functional::char_list.template to_integral(); } + [[nodiscard]] constexpr auto operator""_u64() noexcept + -> operands::OperandLiteralIntegral.template to_integral()> // + { + return {}; + } + + template + requires requires { functional::char_list.template to_floating_point(); } + [[nodiscard]] constexpr auto operator""_f() noexcept + -> operands::OperandLiteralFloatingPoint< + functional::char_list.template to_floating_point(), + functional::char_list.denominator_length() + > { return {}; } + + template + requires requires { functional::char_list.template to_floating_point(); } + [[nodiscard]] constexpr auto operator""_d() noexcept + -> operands::OperandLiteralFloatingPoint< + functional::char_list.template to_floating_point(), + functional::char_list.denominator_length() + > { return {}; } + + template + requires requires { functional::char_list.template to_floating_point(); } + [[nodiscard]] constexpr auto operator""_ld() noexcept + -> operands::OperandLiteralFloatingPoint< + functional::char_list.template to_floating_point(), + functional::char_list.denominator_length() + > { return {}; } + + // We can't construct OperandIdentityBoolean directly here, because we don't know the result of the comparison yet. + [[nodiscard]] constexpr auto operator""_b(const char* name, const std::size_t size) noexcept -> operands::OperandIdentityBoolean::value_type // + { + return {.string = {name, size}}; + } + + [[nodiscard]] constexpr auto operator""_s(const char* name, const std::size_t size) noexcept -> operands::OperandIdentityString // + { + const operands::OperandIdentityString::value_type value{.string = {name, size}}; + return operands::OperandIdentityString{value}; + } + } // namespace literals +} diff --git a/src/unit_test/unit_test.ixx b/src/unit_test/unit_test.ixx deleted file mode 100644 index ddc7ee49..00000000 --- a/src/unit_test/unit_test.ixx +++ /dev/null @@ -1,4936 +0,0 @@ -// This file is part of prometheus -// Copyright (C) 2022-2024 Life4gal -// This file is subject to the license terms in the LICENSE file -// found in the top-level directory of this distribution. - -// The code below is based on boost-ext/ut(https://github.com/boost-ext/ut)(http://www.boost.org/LICENSE_1_0.txt) - -#if GAL_PROMETHEUS_USE_MODULE -module; - -#include - -export module gal.prometheus.unit_test; - -import std; -import gal.prometheus.functional; -import gal.prometheus.meta; -GAL_PROMETHEUS_ERROR_IMPORT_DEBUG_MODULE - -#else -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -// todo -#if __has_include() -#include -#endif - -#include -#include -#include -#include GAL_PROMETHEUS_ERROR_DEBUG_MODULE - -#endif - -namespace gal::prometheus::unit_test -{ - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - - struct color_type - { - std::string_view none = "\033[0m"; - - std::string_view fail = "\033[31m\033[7m"; - std::string_view pass = "\033[32m\033[7m"; - std::string_view skip = "\033[33m\033[7m"; - std::string_view fatal = "\033[35m\033[7m"; - - std::string_view suite = "\033[34m\033[7m"; - std::string_view test = "\033[36m\033[7m"; - std::string_view expression = "\033[38;5;207m\033[7m"; - std::string_view message = "\033[38;5;27m\033[7m"; - }; - - using clock_type = std::chrono::high_resolution_clock; - using time_point_type = clock_type::time_point; - using time_difference_type = std::chrono::milliseconds; - - struct test_result_type; - using test_results_type = std::vector; - - struct test_result_type - { - enum class Status - { - PENDING, - - PASSED, - FAILED, - SKIPPED, - FATAL, - }; - - std::string name; - - test_result_type* parent; - test_results_type children; - - Status status; - time_point_type time_start; - time_point_type time_end; - std::size_t total_assertions_passed; - std::size_t total_assertions_failed; - }; - - constexpr std::string_view anonymous_suite_name{"anonymous_suite"}; - - struct suite_result_type - { - std::string name; - - std::string report_string; - - test_results_type test_results; - }; - - /** - * result: std::vector { - * anonymous_suite: suite - * user_suite_0: suite - * user_suite_1: suite - * user_suite_2: suite - * user_suite_3: suite - * user_suite_n: suite - * } - * - * *_suite_*: suite { - * name: std::string - * user_test_0: test - * user_test_1: test - * user_test_2: test - * user_test_3: test - * user_test_n: test - * } - * - * *_test_*: test { - * name: std::string - * parent: test* - * children(nested test): std::vector - * - * status: Status - * time_start: time_point_type - * time_end: time_point_type - * total_assertions_passed: std::size_t - * total_assertions_failed: std::size_t - * } - */ - // assume suite_results.front() == anonymous_suite - using suite_results_type = std::vector; - - enum class OutputLevel - { - NONE = 0, - // Only the results of each suite execution are output. - RESULT_ONLY = 1, - // RESULT_ONLY + the expression of each test. - INCLUDE_EXPRESSION = 2, - // INCLUDE_EXPRESSION + the source location of each expression. - INCLUDE_EXPRESSION_LOCATION = 3, - }; - - struct config_type - { - using name_type = std::string_view; - using category_type = std::string_view; - using categories_type = std::vector; - - color_type color; - - // terminate the program after n failed assertions (per suite). - // if abort_after_n_failures == 0: - // terminate the program immediately if the assertion fails - std::size_t abort_after_n_failures = std::numeric_limits::max(); - - OutputLevel output_level = OutputLevel::INCLUDE_EXPRESSION_LOCATION; - bool dry_run = false; - - // how to terminate the program - std::function terminator = []() -> void - { - std::exit(-1); // NOLINT(concurrency-mt-unsafe) - }; - - std::function message_reporter = [](const std::string_view report_message) -> void - { - std::cout << report_message; - }; - - // Used to filter the suite/test cases that need to be executed. - std::function filter_execute_suite_name = []([[maybe_unused]] const name_type suite_name) noexcept -> bool - { - return true; - }; - std::function filter_execute_test_name = []([[maybe_unused]] const name_type test_name) noexcept -> bool - { - return true; - }; - std::function filter_execute_test_categories = [](const categories_type& categories) noexcept -> bool - { - if (std::ranges::contains(categories, "skip")) { return false; } - - return true; - }; - - [[noreturn]] auto terminate() const noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(terminator); - - terminator(); - std::exit(-1); // NOLINT(concurrency-mt-unsafe) - } - - auto report_message(const std::string_view message) const noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(message_reporter); - - message_reporter(message); - } - - [[nodiscard]] auto is_suite_execute_required(const name_type suite_name) const noexcept -> bool // - { - return filter_execute_suite_name(suite_name); - } - - [[nodiscard]] auto is_test_execute_required(const name_type test_name, const categories_type& categories) const noexcept -> bool // - { - return filter_execute_test_name(test_name) and filter_execute_test_categories(categories); - } - }; - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END - - template - struct is_expression - : std::bool_constant< - // implicit - std::is_convertible_v or - // explicit - std::is_constructible_v - > {}; - - template - constexpr auto is_expression_v = is_expression::value; - template - concept expression_t = is_expression_v; - - namespace events - { - using name_type = std::string_view; - - struct none {}; - - class Event {}; - - template - constexpr auto is_event_v = std::is_base_of_v; - template - concept event_t = is_event_v; - - GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH - - #if defined(GAL_PROMETHEUS_COMPILER_GNU) or defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // struct bar {}; - // struct foo : bar - // { - // int a; - // }; - // foo f{.a = 42}; // <-- warning: missing initializer for member `foo::` [-Wmissing-field-initializers] - GAL_PROMETHEUS_COMPILER_DISABLE_WARNING(-Wmissing-field-initializers) - #endif - - // ========================================= - // SUITE - // ========================================= - - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventSuiteBegin : public Event - { - public: - name_type name; - }; - - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventSuiteEnd : public Event - { - public: - name_type name; - }; - - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventSuite : public Event - { - public: - using suite_type = void (*)(); - - name_type name; - suite_type suite; - - constexpr auto operator()() -> void { std::invoke(suite); } - constexpr auto operator()() const -> void { std::invoke(suite); } - - private: - [[nodiscard]] constexpr explicit operator EventSuiteBegin() const noexcept { return {.name = name}; } - - [[nodiscard]] constexpr explicit operator EventSuiteEnd() const noexcept { return {.name = name}; } - - public: - [[nodiscard]] constexpr auto begin() const noexcept -> EventSuiteBegin { return this->operator EventSuiteBegin(); } - - [[nodiscard]] constexpr auto end() const noexcept -> EventSuiteEnd { return this->operator EventSuiteEnd(); } - }; - - // ========================================= - // TEST - // ========================================= - - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventTestBegin : public Event - { - public: - name_type name; - }; - - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventTestSkip : public Event - { - public: - name_type name; - }; - - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventTestEnd : public Event - { - public: - name_type name; - }; - - template - requires std::is_invocable_v or std::is_invocable_v - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventTest : public Event - { - public: - using invocable_type = InvocableType; - using arg_type = Arg; - - name_type name; - config_type::categories_type categories; - - mutable invocable_type invocable; - mutable arg_type arg; - - constexpr auto operator()() const -> void - { - return [](I&& i, A&& a) -> void - { - if constexpr (requires { std::invoke(std::forward(i)); }) { std::invoke(std::forward(i)); } - else if constexpr (requires { std::invoke(std::forward(i), std::forward(a)); }) - { - std::invoke(std::forward(i), std::forward(a)); - } - else if constexpr (requires { std::invoke(i.template operator()()); }) { std::invoke(i.template operator()()); } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - }(invocable, arg); - } - - private: - [[nodiscard]] constexpr explicit operator EventTestBegin() const noexcept { return {.name = name}; } - - [[nodiscard]] constexpr explicit operator EventTestEnd() const noexcept { return {.name = name}; } - - [[nodiscard]] constexpr explicit operator EventTestSkip() const noexcept { return {.name = name}; } - - public: - [[nodiscard]] constexpr auto begin() const noexcept -> EventTestBegin { return this->operator EventTestBegin(); } - - [[nodiscard]] constexpr auto end() const noexcept -> EventTestEnd { return this->operator EventTestEnd(); } - - [[nodiscard]] constexpr auto skip() const noexcept -> EventTestSkip { return this->operator EventTestSkip(); } - }; - - // ========================================= - // ASSERTION - // ========================================= - - template - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventAssertionPass : public Event - { - public: - using expression_type = Expression; - - expression_type expression; - std::source_location location; - }; - - template - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventAssertionFail : public Event - { - public: - using expression_type = Expression; - - expression_type expression; - std::source_location location; - }; - - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventAssertionFatal : public Event - { - public: - std::source_location location; - }; - - template - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventAssertionFatalSkip : public Event - { - public: - using expression_type = Expression; - - expression_type expression; - std::source_location location; - }; - - template - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventAssertion : public Event - { - public: - using expression_type = Expression; - - expression_type expression; - std::source_location location; - - private: - [[nodiscard]] constexpr explicit operator EventAssertionPass() const noexcept - { - return {.expression = expression, .location = location}; - } - - [[nodiscard]] constexpr explicit operator EventAssertionFail() const noexcept - { - return {.expression = expression, .location = location}; - } - - [[nodiscard]] constexpr explicit operator EventAssertionFatal() const noexcept { return {.location = location}; } - - [[nodiscard]] constexpr explicit operator EventAssertionFatalSkip() const noexcept - { - return {.expression = expression, .location = location}; - } - - public: - [[nodiscard]] constexpr auto pass() const noexcept -> EventAssertionPass - { - // fixme: Compiler Error: C2273 - // return this->operator EventAssertionPass(); - return operator EventAssertionPass(); - } - - [[nodiscard]] constexpr auto fail() const noexcept -> EventAssertionFail - { - // fixme: Compiler Error: C2273 - // return this->operator EventAssertionFail(); - return operator EventAssertionFail(); - } - - [[nodiscard]] constexpr auto fatal() const noexcept -> EventAssertionFatal - { - // fixme: Compiler Error: C2273 - // return this->operator EventAssertionFatal(); - return operator EventAssertionFatal(); - } - - [[nodiscard]] constexpr auto fatal_skip() const noexcept -> EventAssertionFatalSkip - { - // fixme: Compiler Error: C2273 - // return this->operator EventAssertionFatalSkip(); - return operator EventAssertionFatalSkip(); - } - }; - - // ========================================= - // EXCEPTION - // ========================================= - - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventException : public Event - { - public: - std::string_view message; - - [[nodiscard]] constexpr auto what() const noexcept -> std::string_view { return message; } - }; - - // ========================================= - // LOG - // ========================================= - - template - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventLog : public Event - { - public: - using message_type = MessageType; - - message_type message; - }; - - EventLog(const char*) -> EventLog>; - template - EventLog(const char (&)[N]) -> EventLog>; - EventLog(const wchar_t*) -> EventLog>; - template - EventLog(const wchar_t (&)[N]) -> EventLog>; - EventLog(const char8_t*) -> EventLog>; - template - EventLog(const char8_t (&)[N]) -> EventLog>; - EventLog(const char16_t*) -> EventLog>; - template - EventLog(const char16_t (&)[N]) -> EventLog>; - EventLog(const char32_t*) -> EventLog>; - template - EventLog(const char32_t (&)[N]) -> EventLog>; - - // ========================================= - // SUMMARY - // ========================================= - - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE EventSummary : public Event {}; - - GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH - } // namespace events - - namespace operands - { - class Operand - { - public: - // magic - using prefer_no_type_name = int; - }; - - template - constexpr auto is_operand_v = std::is_base_of_v; - template - concept operand_t = is_operand_v; - - template - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandValue : public Operand - { - public: - using value_type = T; - - private: - value_type value_; - - public: - constexpr explicit(false) OperandValue(const value_type value) // - noexcept(std::is_nothrow_copy_constructible_v) // - requires(std::is_trivially_copy_constructible_v) - : value_{value} {} - - constexpr explicit(false) OperandValue(const value_type& value) // - noexcept(std::is_nothrow_copy_constructible_v) // - requires( - not std::is_trivially_copy_constructible_v and - std::is_copy_constructible_v) - : value_{value} {} - - constexpr explicit(false) OperandValue(value_type&& value) noexcept(std::is_nothrow_move_constructible_v) // - requires( - not std::is_trivially_copy_constructible_v and - std::is_move_constructible_v) - : value_{std::move(value)} {} - - template - requires(std::is_trivially_constructible_v) - constexpr explicit(false) OperandValue(const value_type value) // - noexcept(std::is_nothrow_constructible_v) // - : value_{value} {} - - template - requires( - not std::is_trivially_constructible_v and - std::is_constructible_v) - constexpr explicit(false) OperandValue(const U& value) // - noexcept(std::is_nothrow_constructible_v) // - : value_{value} {} - - template - requires( - not std::is_trivially_constructible_v and - std::is_constructible_v) - constexpr explicit(false) OperandValue(U&& value) // - noexcept(std::is_nothrow_constructible_v) - : value_{std::forward(value)} {} - - template - requires((sizeof...(Args) != 1) and std::is_constructible_v) - constexpr explicit OperandValue(Args&&... args) // - noexcept(std::is_nothrow_constructible_v) - : value_{std::forward(args)...} {} - - [[nodiscard]] constexpr auto value() noexcept -> value_type& { return value_; } - - [[nodiscard]] constexpr auto value() const noexcept -> const value_type& { return value_; } - - [[nodiscard]] constexpr auto to_string() const noexcept -> std::string { return meta::to_string(value_); } - }; - - template - OperandValue(T) -> OperandValue; - - template - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandValueRef : public Operand - { - public: - using value_type = T; - - private: - std::reference_wrapper ref_; - - public: - constexpr explicit(false) OperandValueRef(value_type& ref) noexcept - : ref_{ref} {} - - [[nodiscard]] constexpr auto value() noexcept -> value_type& { return ref_.get(); } - - [[nodiscard]] constexpr auto value() const noexcept -> const value_type& { return ref_.get(); } - - [[nodiscard]] constexpr auto to_string() const noexcept -> std::string { return meta::to_string(ref_.get()); } - }; - - template - OperandValueRef(T&) -> OperandValueRef; - // propagate const - template - OperandValueRef(const T&) -> OperandValueRef; - - template - struct is_operand_value : std::false_type {}; - - template - struct is_operand_value> : std::true_type {}; - - template - struct is_operand_value> : std::true_type {}; - - template - constexpr auto is_operand_value_v = is_operand_value::value; - template - concept operand_value_t = is_operand_value_v; - - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandLiteral : public Operand {}; - - template - constexpr auto is_operand_literal_v = std::is_base_of_v; - template - concept operand_literal_t = is_operand_literal_v; - - template - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandLiteralCharacter : public OperandLiteral - { - public: - using value_type = char; - - constexpr static auto value = Value; - - [[nodiscard]] constexpr static auto to_string() noexcept -> std::string { return meta::to_string(value); } - }; - - template - struct is_operand_literal_character : std::false_type {}; - - template - struct is_operand_literal_character> : std::true_type {}; - - template - constexpr auto is_operand_literal_character_v = is_operand_literal_character::value; - template - concept operand_literal_character_t = is_operand_literal_character_v; - - template - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandLiteralIntegral : public OperandLiteral - { - public: - using value_type = std::remove_cvref_t; - - constexpr static value_type value = Value; - - [[nodiscard]] constexpr auto operator-() const noexcept -> OperandLiteralIntegral<-static_cast>(value)> - { - return {}; - } - - [[nodiscard]] constexpr static auto to_string() noexcept -> std::string { return meta::to_string(value); } - }; - - template - struct is_operand_literal_integral : std::false_type {}; - - template - struct is_operand_literal_integral> : std::true_type {}; - - template - constexpr auto is_operand_literal_integral_v = is_operand_literal_integral::value; - template - concept operand_literal_integral_t = is_operand_literal_integral_v; - - template - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandLiteralFloatingPoint : public OperandLiteral - { - public: - using value_type = std::remove_cvref_t; - - constexpr static value_type value = Value; - constexpr static std::size_t denominator_size = DenominatorSize; - constexpr static value_type epsilon = [](std::size_t n) noexcept -> value_type - { - auto epsilon = static_cast(1); - while (n--) { epsilon /= static_cast(10); } - return epsilon; - }(DenominatorSize); - - [[nodiscard]] constexpr auto operator-() const noexcept -> OperandLiteralFloatingPoint<-value, DenominatorSize> { return {}; } - - [[nodiscard]] constexpr static auto to_string() noexcept -> std::string { return std::format("{:.{}g}", DenominatorSize, value); } - }; - - template - struct is_operand_literal_floating_point : std::false_type {}; - - template - struct is_operand_literal_floating_point> : std::true_type {}; - - template - constexpr auto is_operand_literal_floating_point_v = is_operand_literal_floating_point::value; - template - concept operand_literal_floating_point_t = is_operand_literal_floating_point_v; - - template - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandLiteralAuto : public OperandLiteral - { - public: - constexpr static auto char_list = functional::char_list; - - template - struct rep; - - template - struct rep> - { - using type = OperandLiteralCharacter()>; - }; - - template - struct rep> - { - using type = OperandLiteralIntegral::value_type>()>; - }; - - template - struct rep> - { - using type = OperandLiteralFloatingPoint::value_type>, char_list.denominator_length()>; - }; - - template - struct rep - { - using type = std::conditional_t, OperandLiteralCharacter()>, - OperandLiteralIntegral()>>; - }; - - template - struct rep - { - using type = OperandLiteralFloatingPoint(), char_list.denominator_length()>; - }; - - template - using rebind = typename rep>::type; - }; - - template - struct is_operand_literal_auto : std::false_type {}; - - template - struct is_operand_literal_auto> : std::true_type {}; - - template - constexpr auto is_operand_literal_auto_v = is_operand_literal_auto::value; - template - concept operand_literal_auto_t = is_operand_literal_auto_v; - - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandIdentity : public Operand - { - public: - struct boolean - { - std::string_view message; - }; - - using value_type = bool; - using message_type = boolean; - - private: - value_type value_; - message_type message_; - - public: - constexpr OperandIdentity(const value_type value, const message_type message) noexcept - : value_{value}, - message_{message} {} - - [[nodiscard]] constexpr explicit operator bool() const noexcept { return value_; } - - [[nodiscard]] constexpr auto to_string() const noexcept -> std::string_view { return message_.message; } - }; - - enum class ExpressionCategory - { - EQUAL, - APPROX, - NOT_EQUAL, - NOT_APPROX, - GREATER_THAN, - GREATER_EQUAL, - LESS_THAN, - LESS_EQUAL, - LOGICAL_AND, - LOGICAL_OR, - }; - - struct no_epsilon {}; - - template - requires(not(is_operand_literal_auto_v and is_operand_literal_auto_v)) - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandExpression : public Operand - { - public: - constexpr static auto category = Category; - - // If one side of the expression is OperandLiterAuto and the other side is not OperandLiteralXxx then a compile error should be raised. - static_assert((not is_operand_literal_auto_v) or (is_operand_literal_auto_v == is_operand_literal_v)); - static_assert((not is_operand_literal_auto_v) or (is_operand_literal_auto_v == is_operand_literal_v)); - - // using left_type = std::conditional_t, typename Left::template rebind, Left>; - // using right_type = std::conditional_t, typename Right::template rebind, Right>; - using left_type = Left; - using right_type = Right; - using epsilon_type = Epsilon; - - private: - left_type left_; - right_type right_; - epsilon_type epsilon_; - bool result_; - - [[nodiscard]] constexpr auto do_check() const noexcept -> bool - { - const auto do_compare = [&e = epsilon_](const auto& left, const auto& right) noexcept -> bool - { - #if defined(GAL_PROMETHEUS_COMPILER_APPLE_CLANG) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) or defined(GAL_PROMETHEUS_COMPILER_CLANG) - // error : lambda capture 'e' is not used [-Werror,-Wunused-lambda-capture] - (void)e; - #endif - - if constexpr (category == ExpressionCategory::EQUAL) - { - using std::operator==; - return left == right; - } - else if constexpr (category == ExpressionCategory::APPROX) - { - using std::operator-; - using std::operator<; - return functional::abs(left - right) < e; // NOLINT(clang-diagnostic-implicit-int-float-conversion) - } - else if constexpr (category == ExpressionCategory::NOT_EQUAL) - { - using std::operator!=; - return left != right; - } - else if constexpr (category == ExpressionCategory::NOT_APPROX) - { - using std::operator-; - using std::operator<; - return e < functional::abs(left - right); - } - else if constexpr (category == ExpressionCategory::GREATER_THAN) - { - using std::operator>; - return left > right; - } - else if constexpr (category == ExpressionCategory::GREATER_EQUAL) - { - using std::operator>=; - return left >= right; - } - else if constexpr (category == ExpressionCategory::LESS_THAN) - { - using std::operator<; - return left < right; - } - else if constexpr (category == ExpressionCategory::LESS_EQUAL) - { - using std::operator<=; - return left <= right; - } - else if constexpr (category == ExpressionCategory::LOGICAL_AND) { return static_cast(left) and static_cast(right); } - else if constexpr (category == ExpressionCategory::LOGICAL_OR) { return static_cast(left) or static_cast(right); } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - }; - - if constexpr (requires { do_compare(left_type::value, right_type::value); }) - { - return do_compare(left_type::value, right_type::value); - } - else if constexpr (requires { do_compare(left_type::value, right_); }) { return do_compare(left_type::value, right_); } - else if constexpr (requires { do_compare(left_, right_type::value); }) { return do_compare(left_, right_type::value); } - else if constexpr (requires { do_compare(left_, right_); }) { return do_compare(left_, right_); } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE("can not compare."); } - } - - public: - template - constexpr OperandExpression( - L&& left, - R&& right, - E&& epsilon) noexcept - : left_{std::forward(left)}, - right_{std::forward(right)}, - epsilon_{std::forward(epsilon)}, - result_{do_check()} {} - - template - constexpr OperandExpression( - L&& left, - R&& right) noexcept - : left_{std::forward(left)}, - right_{std::forward(right)}, - epsilon_{}, - result_{do_check()} {} - - [[nodiscard]] constexpr explicit operator bool() const noexcept { return result_; } - - [[nodiscard]] constexpr auto to_string() const noexcept -> std::string - { - if constexpr (category == ExpressionCategory::EQUAL) - { - return std::format("{} == {}", meta::to_string(left_), meta::to_string(right_)); - } - else if constexpr (category == ExpressionCategory::APPROX) - { - return std::format("{} ≈≈ {} (+/- {})", meta::to_string(left_), meta::to_string(right_), meta::to_string(epsilon_)); - } - else if constexpr (category == ExpressionCategory::NOT_EQUAL) - { - return std::format("{} != {}", meta::to_string(left_), meta::to_string(right_)); - } - else if constexpr (category == ExpressionCategory::NOT_APPROX) - { - return std::format("{} !≈ {} (+/- {})", meta::to_string(left_), meta::to_string(right_), meta::to_string(epsilon_)); - } - else if constexpr (category == ExpressionCategory::GREATER_THAN) - { - return std::format("{} > {}", meta::to_string(left_), meta::to_string(right_)); - } - else if constexpr (category == ExpressionCategory::GREATER_EQUAL) - { - return std::format("{} >= {}", meta::to_string(left_), meta::to_string(right_)); - } - else if constexpr (category == ExpressionCategory::LESS_THAN) - { - return std::format("{} < {}", meta::to_string(left_), meta::to_string(right_)); - } - else if constexpr (category == ExpressionCategory::LESS_EQUAL) - { - return std::format("{} <= {}", meta::to_string(left_), meta::to_string(right_)); - } - else if constexpr (category == ExpressionCategory::LOGICAL_AND) - { - return std::format("{} and {}", meta::to_string(left_), meta::to_string(right_)); - } - else if constexpr (category == ExpressionCategory::LOGICAL_OR) - { - return std::format("{} or {}", meta::to_string(left_), meta::to_string(right_)); - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); } - } - }; - - template - constexpr auto is_operand_expression_v = false; - template - constexpr auto is_operand_expression_v> = true; - - template - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandThrow : public Operand - { - public: - using exception_type = Exception; - - private: - bool thrown_; - bool caught_; - - public: - template - constexpr explicit OperandThrow(Invocable&& invocable) noexcept - : thrown_{false}, - caught_{false} - { - if constexpr (std::is_same_v) - { - try { std::invoke(std::forward(invocable)); } - catch (...) - { - thrown_ = true; - caught_ = true; - } - } - else - { - try { std::invoke(std::forward(invocable)); } - catch ([[maybe_unused]] const exception_type& exception) - { - thrown_ = true; - caught_ = true; - } - catch (...) - { - thrown_ = true; - caught_ = false; - } - } - } - - [[nodiscard]] constexpr auto thrown() const noexcept -> bool { return thrown_; } - [[nodiscard]] constexpr auto caught() const noexcept -> bool { return caught_; } - - [[nodiscard]] constexpr explicit operator bool() const noexcept { return caught(); } - - [[nodiscard]] constexpr auto to_string() const noexcept -> std::string - { - return std::format( - "throws<{}> -- [{}]", - meta::name_of(), - (not thrown()) - ? "not thrown" - : // - (not caught()) - ? "thrown but not caught" - : // - "caught"); - } - }; - - class GAL_PROMETHEUS_COMPILER_EMPTY_BASE OperandNoThrow : public Operand - { - public: - using exception_type = void; - - private: - bool thrown_; - - public: - template - constexpr explicit OperandNoThrow(Invocable&& invocable) noexcept - : thrown_{false} - { - try { std::invoke(std::forward(invocable)); } - catch (...) { thrown_ = true; } - } - - [[nodiscard]] constexpr explicit operator bool() const noexcept { return not thrown_; } - - [[nodiscard]] /*constexpr*/ auto to_string() const noexcept -> std::string { return std::format("nothrow - {:s}", not thrown_); } - }; - } // namespace operands - - namespace executor - { - class Executor - { - struct internal_tag {}; - - public: - // not nullable - using suite_results_iterator_type = suite_results_type::iterator; - // nullable - using test_results_iterator_type = test_results_type::pointer; - - private: - std::shared_ptr config_; - - suite_results_type suite_results_; - - suite_results_iterator_type current_suite_result_; - test_results_iterator_type current_test_result_; - - std::size_t total_fails_exclude_current_test_; - - enum class IdentType - { - TEST, - ASSERTION, - }; - - template - [[nodiscard]] auto nested_level_of_current_test() const noexcept -> std::size_t - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not current_suite_result_->test_results.empty()); - - if constexpr (Type == IdentType::ASSERTION) - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ != nullptr); - } - else - { - // top level - if (current_test_result_ == nullptr) { return 1; } - } - - std::size_t result = 0; - for (const auto* p = current_test_result_; p != nullptr; p = p->parent) { result += 1; } - - return result + (Type == IdentType::ASSERTION); - } - - template - [[nodiscard]] auto ident_size_of_current_test() const noexcept -> std::size_t { return nested_level_of_current_test() * 4; } - - // [suite_name] test1.test2.test3 - [[nodiscard]] auto fullname_of_current_test() const noexcept -> std::string - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(not current_suite_result_->test_results.empty()); - - auto result = std::format("[{}] ", current_suite_result_->name); - - const auto* p = std::addressof(current_suite_result_->test_results.back()); - while (p != nullptr) - { - result.append(p->name); - result.push_back('.'); - - p = p->children.empty() ? nullptr : std::addressof(p->children.back()); - } - - result.pop_back(); - return result; - } - - [[nodiscard]] auto ms_duration_of_current_test() const noexcept -> time_point_type::rep - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ != nullptr); - return std::chrono::duration_cast(current_test_result_->time_end - current_test_result_->time_start).count(); - } - - auto check_fails_may_terminate() noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(config_ != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_suite_result_ != suite_results_.end()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ != nullptr); - - if (current_test_result_->status == test_result_type::Status::FATAL or - total_fails_exclude_current_test_ + current_test_result_->total_assertions_failed > config_->abort_after_n_failures) - [[unlikely]] - { - std::format_to( - std::back_inserter(current_suite_result_->report_string), - "{}Error: fast fail for test `{}` after `{}` failures total.{} \n", - config_->color.fail, - fullname_of_current_test(), - current_test_result_->total_assertions_failed, - config_->color.none - ); - - on(events::EventTestEnd{.name = current_test_result_->name}); - on(events::EventSuiteEnd{.name = current_suite_result_->name}); - // @see Executor::~Executor - // on(events::EventSummary{}); - - config_->terminate(); - } - } - - // ========================================= - // SUITE - // ========================================= - - auto on(const events::EventSuiteBegin& suite_begin) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(config_ != nullptr); - - auto& [name, report_string, test_results] = suite_results_.emplace_back(std::string{suite_begin.name}); - current_suite_result_ = std::ranges::prev(suite_results_.end(), 1); - - std::format_to( - std::back_inserter(report_string), - "Executing suite {}{}{} vvv\n", - config_->color.suite, - name, - config_->color.none); - } - - auto on(const events::EventSuiteEnd& suite_end) -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(config_ != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_suite_result_ != suite_results_.end()); - - // todo: If the user catches the exception, how do we make sure the executor is still available? How to handle the current suite? - if (current_suite_result_->name != suite_end.name) - [[unlikely]] - { - throw std::logic_error{ - std::format( - "can not pop suite because `{}` differs from `{}`", - current_suite_result_->name, - suite_end.name)}; - } - - auto& [name, report_string, test_results] = *current_suite_result_; - - std::format_to( - std::back_inserter(report_string), - "^^^ End of suite {}{}{} execution\n", - config_->color.suite, - name, - config_->color.none); - - // reset to anonymous suite - current_suite_result_ = suite_results_.begin(); - } - - // ========================================= - // TEST - // ========================================= - - auto on(const events::EventTestBegin& test_begin) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(config_ != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_suite_result_ != suite_results_.end()); - - // we chose to construct a temporary object here to avoid possible errors, and trust that the optimizer will forgive us ;) - auto t = test_result_type{ - .name = std::string{test_begin.name}, - .parent = current_test_result_, - .children = {}, - .status = test_result_type::Status::PENDING, - .time_start = clock_type::now(), - .time_end = {}, - .total_assertions_passed = 0, - .total_assertions_failed = 0}; - - if (current_test_result_) - { - // nested - auto& this_test = current_test_result_->children.emplace_back(std::move(t)); - current_test_result_ = std::addressof(this_test); - - if (std::to_underlying(config_->output_level) > std::to_underlying(OutputLevel::NONE)) - { - std::format_to( - std::back_inserter(current_suite_result_->report_string), - "{:{}}Running nested test {}{}{}...\n", - " ", - ident_size_of_current_test(), - config_->color.test, - fullname_of_current_test(), - config_->color.none); - } - } - else - { - // top level - auto& this_test = current_suite_result_->test_results.emplace_back(std::move(t)); - current_test_result_ = std::addressof(this_test); - - if (std::to_underlying(config_->output_level) > std::to_underlying(OutputLevel::NONE)) - { - std::format_to( - std::back_inserter(current_suite_result_->report_string), - "{:{}}Running test {}{}{}...\n", - " ", - ident_size_of_current_test(), - config_->color.test, - fullname_of_current_test(), - config_->color.none); - } - } - } - - auto on(const events::EventTestSkip& test_skip) -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(config_ != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_); - - on(events::EventTestBegin{.name = test_skip.name}); - current_test_result_->status = test_result_type::Status::SKIPPED; - on(events::EventTestEnd{.name = test_skip.name}); - } - - auto on(const events::EventTestEnd& test_end) -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(config_ != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_suite_result_ != suite_results_.end()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ != nullptr); - - // todo: If the user catches the exception, how do we make sure the executor is still available? How to handle the current test and suite? - if (current_test_result_->name != test_end.name) - [[unlikely]] - { - throw std::logic_error{ - std::format( - "can not pop test because `{}` differs from `{}`", - current_test_result_->name, - test_end.name) - }; - } - - current_test_result_->time_end = clock_type::now(); - // if (current_test_result_->status == test_result_type::Status::FATAL) - if (current_test_result_->status == test_result_type::Status::PENDING) - [[likely]] - { - // the current test is considered SKIPPED only if it does not have any assertions and has no children. - if (current_test_result_->total_assertions_failed == 0 and current_test_result_->total_assertions_passed == 0) - { - if (current_test_result_->children.empty()) { current_test_result_->status = test_result_type::Status::SKIPPED; } - else - { - current_test_result_->status = - std::ranges::all_of( - current_test_result_->children, - [](const auto& child_test) noexcept { return child_test.total_assertions_failed == 0; } - ) - ? test_result_type::Status::PASSED - : test_result_type::Status::FAILED; - } - } - else - { - current_test_result_->status = current_test_result_->total_assertions_failed == 0 - ? test_result_type::Status::PASSED - : test_result_type::Status::FAILED; - } - } - - total_fails_exclude_current_test_ += current_test_result_->total_assertions_failed; - - if (std::to_underlying(config_->output_level) > std::to_underlying(OutputLevel::NONE)) - { - if (const auto status = current_test_result_->status; - status == test_result_type::Status::PASSED or status == test_result_type::Status::FAILED) - [[likely]] - { - std::format_to( - std::back_inserter(current_suite_result_->report_string), - "{:{}}{}{}{} after {} milliseconds.\n", - "", - ident_size_of_current_test(), - status == test_result_type::Status::PASSED ? config_->color.pass : config_->color.fail, - status == test_result_type::Status::PASSED ? "PASSED" : "FAILED", - config_->color.none, - ms_duration_of_current_test() - ); - } - else if (status == test_result_type::Status::SKIPPED) - [[unlikely]] - { - std::format_to( - std::back_inserter(current_suite_result_->report_string), - "{:{}}{}SKIPPED{}\n", - "", - ident_size_of_current_test(), - config_->color.skip, - config_->color.none - ); - } - else if (status == test_result_type::Status::FATAL) - [[unlikely]] - { - std::format_to( - std::back_inserter(current_suite_result_->report_string), - "{:{}}{}INTERRUPTED{}\n", - "", - ident_size_of_current_test(), - config_->color.skip, - config_->color.none - ); - } - else { std::unreachable(); } - } - - current_test_result_ = current_test_result_->parent; - } - - // ========================================= - // ASSERTION - // ========================================= - - template - auto on(const events::EventAssertionPass& assertion_pass) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(config_ != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_suite_result_ != suite_results_.end()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ != nullptr); - - if (const auto level = std::to_underlying(config_->output_level); - level >= std::to_underlying(OutputLevel::INCLUDE_EXPRESSION)) - { - // @see: Operand::prefer_no_type_name - constexpr auto prefer_no_type_name = requires { typename Expression::prefer_no_type_name; }; - - if (level >= std::to_underlying(OutputLevel::INCLUDE_EXPRESSION_LOCATION)) - { - std::format_to( - std::back_inserter(current_suite_result_->report_string), - "{:{}}[{}:{}] {}[{}]{} - {}PASSED{} \n", - " ", - ident_size_of_current_test(), - assertion_pass.location.file_name(), - assertion_pass.location.line(), - config_->color.expression, - meta::to_string(assertion_pass.expression), - config_->color.none, - config_->color.pass, - config_->color.none); - } - else - { - std::format_to( - std::back_inserter(current_suite_result_->report_string), - "{:{}} {}[{}]{} - {}PASSED{} \n", - " ", - ident_size_of_current_test(), - config_->color.expression, - meta::to_string(assertion_pass.expression), - config_->color.none, - config_->color.pass, - config_->color.none); - } - } - - current_test_result_->total_assertions_passed += 1; - } - - template - auto on(const events::EventAssertionFail& assertion_fail) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(config_ != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_suite_result_ != suite_results_.end()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ != nullptr); - - if (const auto level = std::to_underlying(config_->output_level); - level >= std::to_underlying(OutputLevel::INCLUDE_EXPRESSION)) - { - // @see: Operand::prefer_no_type_name - constexpr auto prefer_no_type_name = requires { typename Expression::prefer_no_type_name; }; - - if (level >= std::to_underlying(OutputLevel::INCLUDE_EXPRESSION_LOCATION)) - { - std::format_to( - std::back_inserter(current_suite_result_->report_string), - "{:{}}[{}:{}] {}[{}]{} - {}FAILED{} \n", - " ", - ident_size_of_current_test(), - assertion_fail.location.file_name(), - assertion_fail.location.line(), - config_->color.expression, - meta::to_string(assertion_fail.expression), - config_->color.none, - config_->color.fail, - config_->color.none); - } - else - { - std::format_to( - std::back_inserter(current_suite_result_->report_string), - "{:{}} {}[{}]{} - {}FAILED{} \n", - " ", - ident_size_of_current_test(), - config_->color.expression, - meta::to_string(assertion_fail.expression), - config_->color.none, - config_->color.pass, - config_->color.none); - } - } - - current_test_result_->total_assertions_failed += 1; - - check_fails_may_terminate(); - } - - auto on(const events::EventAssertionFatal& assertion_fatal, internal_tag) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(config_ != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_suite_result_ != suite_results_.end()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ != nullptr); - - if (const auto level = std::to_underlying(config_->output_level); - level >= std::to_underlying(OutputLevel::INCLUDE_EXPRESSION)) - { - std::format_to( - std::back_inserter(current_suite_result_->report_string), - "{:{}}^^^ {}FATAL ERROR{}\n", - " ", - ident_size_of_current_test() + - ( - // '[' - 1 + - // file_name - std::string_view::traits_type::length(assertion_fatal.location.file_name())) + - // ':' - 1 + - // line - [](T line) - { - T result = 0; - while (line) - { - result += 1; - line /= 10; - } - return result; - }(assertion_fatal.location.line()) + - // "] [" - 3, - config_->color.fatal, - config_->color.none); - } - - current_test_result_->total_assertions_failed += 1; - current_test_result_->status = test_result_type::Status::FATAL; - - check_fails_may_terminate(); - } - - template - auto on(const events::EventAssertionFatalSkip& assertion_fatal_skip) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(config_ != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_suite_result_ != suite_results_.end()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ != nullptr); - - if (const auto level = std::to_underlying(config_->output_level); - level >= std::to_underlying(OutputLevel::INCLUDE_EXPRESSION)) - { - // @see: Operand::prefer_no_type_name - constexpr auto prefer_no_type_name = requires { typename Expression::prefer_no_type_name; }; - - if (level >= std::to_underlying(OutputLevel::INCLUDE_EXPRESSION_LOCATION)) - { - std::format_to( - std::back_inserter(current_suite_result_->report_string), - "{:{}}[{}:{}] {}[{}]{} - {}SKIPPED{} \n", - " ", - ident_size_of_current_test(), - assertion_fatal_skip.location.file_name(), - assertion_fatal_skip.location.line(), - config_->color.expression, - meta::to_string(assertion_fatal_skip.expression), - config_->color.none, - config_->color.fatal, - config_->color.none); - } - else - { - std::format_to( - std::back_inserter(current_suite_result_->report_string), - "{:{}} {}[{}]{} - {}SKIPPED{} \n", - " ", - ident_size_of_current_test(), - config_->color.expression, - meta::to_string(assertion_fatal_skip.expression), - config_->color.none, - config_->color.fatal, - config_->color.none); - } - } - - current_test_result_->total_assertions_failed += 1; - - check_fails_may_terminate(); - } - - // ========================================= - // EXCEPTION - // ========================================= - - auto on(const events::EventException& exception) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(config_ != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_suite_result_ != suite_results_.end()); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ != nullptr); - - auto& [suite_name, report_string, _] = *current_suite_result_; - const auto& test_name = current_test_result_->name; - - on(events::EventTestEnd{.name = test_name}); - on(events::EventSuiteEnd{.name = suite_name}); - - std::format_to( - std::back_inserter(report_string), - "{}Abort test because unexpected exception with message: {}.{}\n", - config_->color.fail, - exception.what(), - config_->color.none); - - std::ranges::for_each( - suite_results_, - [this](const auto& suite_result) noexcept { config_->report_message(suite_result.report_string); }); - - config_->report_message( - std::format( - "--- early abort for test {}{}{} after {} failures total.", - config_->color.test, - test_name, - config_->color.none, - total_fails_exclude_current_test_)); - - config_->terminate(); - } - - // ========================================= - // LOG - // ========================================= - - template - auto on(const events::EventLog& log, internal_tag) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(config_ != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_suite_result_ != suite_results_.end()); - - if (log.message != "\n") - [[likely]] - { - // pop '\n' - current_suite_result_->report_string.pop_back(); - } - - current_suite_result_->report_string.append(config_->color.message); - #if __cpp_lib_containers_ranges >= 202202L - current_suite_result_->report_string.append_range(log.message); - #else - current_suite_result_->report_string.insert(current_suite_result_->report_string.end(), std::ranges::begin(log.message), std::ranges::end(log.message)); - #endif - current_suite_result_->report_string.append(config_->color.none); - - // push '\n' - current_suite_result_->report_string.push_back('\n'); - } - - // ========================================= - // SUMMARY - // ========================================= - - auto on(const events::EventSummary&) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(config_ != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_suite_result_ != suite_results_.end()); - - if (const auto level = std::to_underlying(config_->output_level); - level >= std::to_underlying(OutputLevel::NONE)) - { - struct total_result - { - std::size_t test_passed; - std::size_t test_failed; - std::size_t test_skipped; - - std::size_t assertion_passed; - std::size_t assertion_failed; - - constexpr auto operator+(const total_result& other) const noexcept -> total_result - { - total_result new_one{*this}; - - new_one.test_passed += other.test_passed; - new_one.test_failed += other.test_failed; - new_one.test_skipped += other.test_skipped; - - new_one.assertion_passed += other.assertion_passed; - new_one.assertion_failed += other.assertion_failed; - - return new_one; - } - }; - - constexpr auto calc_result_of_test = functional::y_combinator{ - [](auto&& self, const test_result_type& test_result) noexcept -> total_result - { - return std::ranges::fold_left( - test_result.children, - total_result{ - .test_passed = (test_result.status == test_result_type::Status::PASSED) ? 1ull : 0, - .test_failed = (test_result.status == test_result_type::Status::FAILED or test_result.status == test_result_type::Status::FATAL) ? 1ull : 0, - .test_skipped = (test_result.status == test_result_type::Status::SKIPPED) ? 1ull : 0, - .assertion_passed = test_result.total_assertions_passed, - .assertion_failed = test_result.total_assertions_failed - }, - [self](const total_result& total, const test_result_type& nested_test_result) noexcept -> total_result - { - return total + self(nested_test_result); - } - ); - }}; - - constexpr auto calc_result_of_suite = [calc_result_of_test](const suite_result_type& suite_result) noexcept -> total_result - { - return std::ranges::fold_left( - suite_result.test_results, - total_result{ - .test_passed = 0, - .test_failed = 0, - .test_skipped = 0, - .assertion_passed = 0, - .assertion_failed = 0 - }, - [calc_result_of_test](const total_result& total, const test_result_type& test_result) noexcept -> total_result - { - // todo: - // \src\infrastructure\unit_test.ixx(1624,26): error : function 'operator()' with deduced return type cannot be used before it is defined - // 1624 | return total + self(nested_test_result); - // | ^ - // \src\infrastructure\unit_test.ixx(1622,10): note: while substituting into a lambda expression here - // 1622 | [self](const total_result& total, const test_result_type& nested_test_result) noexcept -> total_result - // | ^ - // C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.41.33923\include\type_traits(1701,16): note: in instantiation of function template specialization 'gal::prometheus::unit_test::executor::Executor::on(const events::EventSummary &)::(anonymous class)::operator()>' requested here - // 1701 | return static_cast<_Callable&&>(_Obj)(static_cast<_Ty1&&>(_Arg1), static_cast<_Types2&&>(_Args2)...); - // | ^ - // \src\functional\functor.ixx(99,16): note: in instantiation of function template specialization 'std::invoke &, const gal::prometheus::unit_test::test_result_type &>' requested here - // 99 | return std::invoke(function, *this, std::forward(args)...); - // | ^ - // \src\infrastructure\unit_test.ixx(1643,43): note: in instantiation of function template specialization 'gal::prometheus::functional::y_combinator<(lambda at \src\infrastructure\unit_test.ixx:1607:8)>::operator()' requested here - // 1643 | return total + calc_result_of_test(test_result); - // | ^ - // clang 17.0.1: OK - // clang 17.0.3: ERROR - // clang trunk: ERROR - return total + calc_result_of_test(test_result); - }); - }; - - std::ranges::for_each( - suite_results_, - [&color = config_->color, c = config_, calc_result_of_suite](suite_result_type& suite_result) noexcept -> void - { - // ReSharper disable once CppUseStructuredBinding - if (const auto result = calc_result_of_suite(suite_result); - result.assertion_failed == 0) - [[likely]] - { - std::format_to( - std::back_inserter(suite_result.report_string), - "\n==========================================\n" - "Suite {}{}{} -> {}all tests passed{}({} assertions in {} tests), {} tests skipped." - "\n==========================================\n", - color.suite, - suite_result.name, - color.none, - color.pass, - color.none, - result.assertion_passed, - result.test_passed, - result.test_skipped); - } - else - [[unlikely]] - { - std::format_to( - std::back_inserter(suite_result.report_string), - "\n==========================================\n" - "Suite {}{}{}\n" - "tests {} | {} {}passed({:.6g}%){} | {} {}failed({:.6g}%){} | {} {}skipped({:.6g}%){}\n" - "assertions {} | {} {}passed({:.6g}%){} | {} {}failed({:.6g}%){}" - "\n==========================================\n", - color.suite, - suite_result.name, - color.none, - // test - result.test_passed + result.test_failed + result.test_skipped, - // passed - result.test_passed, - color.pass, - static_cast(result.test_passed) / - static_cast(result.test_passed + result.test_failed + result.test_skipped) - * 100.0, - color.none, - // failed - result.test_failed, - color.fail, - static_cast(result.test_failed) / - static_cast(result.test_passed + result.test_failed + result.test_skipped) - * 100.0, - color.none, - // skipped - result.test_skipped, - color.skip, - static_cast(result.test_skipped) / - static_cast(result.test_passed + result.test_failed + result.test_skipped) - * 100.0, - color.none, - // assertion - result.assertion_passed + result.assertion_failed, - // passed - result.assertion_passed, - color.pass, - static_cast(result.assertion_passed) / - static_cast(result.assertion_passed + result.assertion_failed) - * 100.0, - color.none, - // failed - result.assertion_failed, - color.fail, - static_cast(result.assertion_failed) / - static_cast(result.assertion_passed + result.assertion_failed) - * 100.0, - color.none); - } - - c->report_message(suite_result.report_string); - }); - } - } - - public: - Executor(const Executor&) noexcept = delete; - Executor(Executor&&) noexcept = delete; - auto operator=(const Executor&) noexcept -> Executor& = delete; - auto operator=(Executor&&) noexcept -> Executor& = delete; - - explicit Executor() - : config_{std::make_shared()}, - current_suite_result_{suite_results_.end()}, - current_test_result_{nullptr}, - total_fails_exclude_current_test_{0} - { - // we chose to construct a temporary object here to avoid possible errors, and trust that the optimizer will forgive us ;) - auto t = suite_result_type{ - .name = std::string{anonymous_suite_name}, - .report_string = {}, - .test_results = {}}; - - suite_results_.emplace_back(std::move(t)); - current_suite_result_ = suite_results_.begin(); - } - - ~Executor() noexcept - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(config_ != nullptr); - - if (not config_->dry_run) { on(events::EventSummary{}); } - } - - [[nodiscard]] auto config() const noexcept -> config_type& - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(config_ != nullptr); - - return *config_; - } - - // ========================================= - // SUITE - // ========================================= - - auto on(const events::EventSuite& suite) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(config_ != nullptr); - - if (config_->is_suite_execute_required(suite.name)) - { - on(suite.begin()); - - // throwable - std::invoke(suite); - - on(suite.end()); - } - } - - // ========================================= - // TEST - // ========================================= - - template - auto on(const events::EventTest& test) noexcept -> void - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(config_ != nullptr); - - if (config_->is_test_execute_required(test.name, test.categories)) - { - this->on(test.begin()); - - try // - { - std::invoke(test); - } - catch (const std::exception& exception) { on(events::EventException{.message = exception.what()}); } - catch (...) { on(events::EventException{.message = "unhandled exception, not derived from std::exception"}); } - - this->on(test.end()); - } - else { this->on(test.skip()); } - } - - // ========================================= - // ASSERTION - // ========================================= - - template - auto on(const events::EventAssertion& assertion) noexcept -> bool - { - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(config_ != nullptr); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(current_test_result_ != nullptr); - - if (config_->dry_run) - [[unlikely]] - { - return true; - } - - if (current_test_result_->status == test_result_type::Status::FATAL) - [[unlikely]] - { - this->on(assertion.fatal_skip()); - // Consider the test case execution successful and avoid undesired log output. - return true; - } - - if (static_cast(assertion.expression)) - [[likely]] - { - this->on(assertion.pass()); - return true; - } - - this->on(assertion.fail()); - return false; - } - - auto on(const events::EventAssertionFatal& assertion_fatal) noexcept -> void - { - on(assertion_fatal, internal_tag{}); - // - } - - // ========================================= - // LOG - // ========================================= - - template - auto on(const events::EventLog& log) noexcept -> void - { - this->on(log, internal_tag{}); - // - } - }; - - [[nodiscard]] inline auto executor() noexcept -> Executor& - { - static Executor executor{}; - return executor; - } - } // namespace executor - - namespace dispatcher - { - namespace detail - { - template - struct dispatched_expression - { - using expression_type = Lhs; - using dispatcher_type = Dispatcher; - - expression_type expression; - - [[nodiscard]] constexpr explicit operator bool() const noexcept // - { - return static_cast(expression); - } - }; - - template - struct is_dispatched_expression : std::false_type {}; - - template - struct is_dispatched_expression> : std::true_type {}; - - template - constexpr auto is_dispatched_expression_v = is_dispatched_expression::value; - template - concept dispatched_expression_t = is_dispatched_expression_v; - - template - struct is_type : std::bool_constant, std::remove_cvref_t>> {}; - - template - constexpr auto is_type_v = is_type::value; - - template - concept type_t = is_type_v; - - template - struct is_type_or_dispatched_type : std::bool_constant< - // In most cases the following line will already fulfill the requirement - is_type_v or - // The next two lines allow us to support more complex expressions, such as `(xxx == xxx) == "same!"_b)` - // implicit - std::is_convertible_v, std::remove_cvref_t> or - // explicit - std::is_constructible_v, std::remove_cvref_t> - > {}; - - template - struct is_type_or_dispatched_type, RequiredType> : is_type_or_dispatched_type {}; - - template - constexpr auto is_type_or_dispatched_type_v = is_type_or_dispatched_type::value; - - template - concept type_or_dispatched_type_t = is_type_or_dispatched_type_v; - - template typename Constrain> - struct is_constrain_satisfied_type_or_dispatched_type : std::bool_constant>::value> {}; - - template typename Constrain, typename Dispatcher> - struct is_constrain_satisfied_type_or_dispatched_type, Constrain> : - is_constrain_satisfied_type_or_dispatched_type::expression_type, Constrain> {}; - - template typename Constrain> - constexpr auto is_constrain_satisfied_type_or_dispatched_type_v = is_constrain_satisfied_type_or_dispatched_type::value; - - template typename Constrain> - concept constrain_satisfied_type_or_dispatched_type_t = is_constrain_satisfied_type_or_dispatched_type_v; - - template typename Constrain> - struct constrain_against_wrapper - { - template - struct rebind - { - constexpr static auto value = not Constrain::value; - }; - }; - - template typename Constrain> - constexpr auto is_constrain_against_type_or_dispatched_type_v = is_constrain_satisfied_type_or_dispatched_type_v< - T, - constrain_against_wrapper::template rebind - >; - - template typename Constrain> - concept constrain_against_type_or_dispatched_type_t = is_constrain_against_type_or_dispatched_type_v; - - struct type_or_dispatched_type - { - template - [[nodiscard]] constexpr static auto get(T&& v) noexcept -> decltype(auto) // - { - if constexpr (is_dispatched_expression_v>) // - { - return std::forward(v).expression; - } - else // - { - return std::forward(v); - } - } - }; - - template - struct lazy_dispatcher_type; - - template - struct lazy_dispatcher_type - { - using type = typename L::dispatcher_type; - }; - - template - struct lazy_dispatcher_type - { - using type = typename R::dispatcher_type; - }; - - template - using lazy_dispatcher_type_t = typename lazy_dispatcher_type::type; - - template - struct lazy_expression_type; - - template - struct lazy_expression_type - { - using type = typename T::expression_type; - }; - - template - struct lazy_expression_type - { - using type = T; - }; - - template - using lazy_expression_type_t = typename lazy_expression_type::type; - - // ============================================ - // operator== - // ============================================ - - // OperandValue - - // floating_point == value{...} - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator==(const L& lhs, const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::APPROX, - left_expression_type, - typename right_expression_type::value_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - type_or_dispatched_type::get(rhs).value(), - std::numeric_limits::epsilon() - } - }; - } - - // value{...} == floating_point - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator==(const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs == lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::APPROX, - typename left_expression_type::value_type, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs).value(), - type_or_dispatched_type::get(rhs), - std::numeric_limits::epsilon() - } - }; - } - - // not(floating_point) == value{...} - template< - constrain_against_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator==(const L& lhs, const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::EQUAL, - left_expression_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - type_or_dispatched_type::get(rhs).value() - } - }; - } - - // value{...} == not(floating_point) - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_against_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator==(const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs == lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::EQUAL, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs).value(), - type_or_dispatched_type::get(rhs) - } - }; - } - - // OperandLiteralXxx - - // character == "xxx"_c - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator==(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::EQUAL, - left_expression_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - right_expression_type::value - } - }; - } - - // "xxx"_c == character - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator==([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs == lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::EQUAL, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - left_expression_type::value, - type_or_dispatched_type::get(rhs) - } - }; - } - - // integral == "xxx"_x - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator==(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::EQUAL, - left_expression_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - right_expression_type::value - } - }; - } - - // "xxx"_x == integral - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator==([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs == lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::EQUAL, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - left_expression_type::value, - type_or_dispatched_type::get(rhs) - } - }; - } - - // floating_point == "xxx"_x - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator==(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::APPROX, - left_expression_type, - typename right_expression_type::value_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - right_expression_type::value, - right_expression_type::epsilon - } - }; - } - - // "xxx"_x == floating_point - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator==([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs == lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::APPROX, - typename left_expression_type::value_type, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - left_expression_type::value, - type_or_dispatched_type::get(rhs), - left_expression_type::epsilon - } - }; - } - - // ? == "xxx"_auto - template< - typename L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - // rebind-able type - and requires - { - typename lazy_expression_type_t, R>::template rebind< - lazy_expression_type_t, L> - >; - } - [[nodiscard]] constexpr auto operator==(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - // forward - // lhs == dispatched_expression{} - return lhs == dispatched_expression, dispatcher_type>{}; - } - - // "xxx"_auto == ? - template< - constrain_satisfied_type_or_dispatched_type_t L, - typename R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - // rebind-able type - and requires - { - typename lazy_expression_type_t, L>::template rebind< - lazy_expression_type_t, R> - >; - } - [[nodiscard]] constexpr auto operator==([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs == lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - // forward - // dispatched_expression{} == rhs - return dispatched_expression, dispatcher_type>{} == rhs; - } - - // OperandIdentity - - // bool == operands::OperandIdentity::message_type{...} - template< - type_or_dispatched_type_t L, - // not allowed to be a candidate for an expression such as `xxx == "xxx"`, must be `xxx == "xxx"_b` - type_t R - > - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator==(const L& lhs, const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - return dispatched_expression< - operands::OperandIdentity, - dispatcher_type - >{ - .expression = { - // (xxx == xxx) == "xxx"_b => (operands::OperandExpression == operands::OperandIdentity::message_type) - // explicit cast(operands::OperandExpression => operands::OperandIdentity::value_type) - static_cast(type_or_dispatched_type::get(lhs)), - type_or_dispatched_type::get(rhs) - } - }; - } - - // operands::OperandIdentity::message_type{...} == bool - template< - // not allowed to be a candidate for an expression such as `"xxx" == xxx`, must be `"xxx"_b == xxx` - type_t L, - type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator==(const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - return rhs == lhs; - } - - // ============================================ - // operator!= - // ============================================ - - // OperandValue - - // floating_point != value{...} - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator!=(const L& lhs, const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::NOT_APPROX, - left_expression_type, - typename right_expression_type::value_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - type_or_dispatched_type::get(rhs).value(), - std::numeric_limits::epsilon() - } - }; - } - - // value{...} != floating_point - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator!=(const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs != lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::NOT_APPROX, - typename left_expression_type::value_type, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs).value(), - type_or_dispatched_type::get(rhs), - std::numeric_limits::epsilon() - } - }; - } - - // not(floating_point) != value{...} - template< - constrain_against_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator!=(const L& lhs, const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::NOT_EQUAL, - left_expression_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - type_or_dispatched_type::get(rhs).value() - } - }; - } - - // value{...} != not(floating_point) - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_against_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator!=(const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs != lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::NOT_EQUAL, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs).value(), - type_or_dispatched_type::get(rhs) - } - }; - } - - // OperandLiteralXxx - - // character != "xxx"_c - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator!=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::NOT_EQUAL, - left_expression_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - right_expression_type::value - } - }; - } - - // "xxx"_c != character - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator!=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs != lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::NOT_EQUAL, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - left_expression_type::value, - type_or_dispatched_type::get(rhs) - } - }; - } - - // integral != "xxx"_x - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator!=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::NOT_EQUAL, - left_expression_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - right_expression_type::value - } - }; - } - - // "xxx"_x != integral - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator!=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs != lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::NOT_EQUAL, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - left_expression_type::value, - type_or_dispatched_type::get(rhs) - } - }; - } - - // floating_point != "xxx"_x - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator!=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::NOT_APPROX, - left_expression_type, - typename right_expression_type::value_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - right_expression_type::value, - right_expression_type::epsilon - } - }; - } - - // "xxx"_x != floating_point - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator!=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs != lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::NOT_APPROX, - typename left_expression_type::value_type, - right_expression_type, - typename left_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - left_expression_type::value, - type_or_dispatched_type::get(rhs), - left_expression_type::epsilon - } - }; - } - - // ? != "xxx"_auto - template< - typename L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - // rebind-able type - and requires - { - typename lazy_expression_type_t, R>::template rebind< - lazy_expression_type_t, L> - >; - } - [[nodiscard]] constexpr auto operator!=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - // forward - // lhs != dispatched_expression{} - return lhs != dispatched_expression, dispatcher_type>{}; - } - - // "xxx"_auto != ? - template< - constrain_satisfied_type_or_dispatched_type_t L, - typename R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - // rebind-able type - and requires - { - typename lazy_expression_type_t, L>::template rebind< - lazy_expression_type_t, R> - >; - } - [[nodiscard]] constexpr auto operator!=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs != lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - // forward - // lhs != dispatched_expression{} - return dispatched_expression, dispatcher_type>{} != rhs; - } - - // OperandIdentity - - // bool != operands::OperandIdentity::message_type{...} - template< - type_or_dispatched_type_t L, - type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator!=(const L& lhs, const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - return dispatched_expression< - operands::OperandIdentity, - dispatcher_type - >{ - .expression = { - // (xxx == xxx) != "xxx"_b => (operands::OperandExpression != operands::OperandIdentity::message_type) - // explicit cast(operands::OperandExpression => operands::OperandIdentity::value_type) - not static_cast(type_or_dispatched_type::get(lhs)), - type_or_dispatched_type::get(rhs) - } - }; - } - - // operands::OperandIdentity::message_type{...} != bool - template< - type_or_dispatched_type_t L, - type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator!=(const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - return rhs != lhs; - } - - // ============================================ - // operator> - // ============================================ - - // OperandValue - - // floating_point > value{...} - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>(const L& lhs, const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_THAN, - left_expression_type, - typename right_expression_type::value_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - type_or_dispatched_type::get(rhs).value(), - std::numeric_limits::epsilon() - } - }; - } - - // value{...} > floating_point - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>(const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs > lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_THAN, - typename left_expression_type::value_type, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs).value(), - type_or_dispatched_type::get(rhs), - std::numeric_limits::epsilon() - } - }; - } - - // not(floating_point) > value{...} - template< - constrain_against_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>(const L& lhs, const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_THAN, - left_expression_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - type_or_dispatched_type::get(rhs).value() - } - }; - } - - // value{...} > not(floating_point) - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_against_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>(const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs > lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_THAN, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs).value(), - type_or_dispatched_type::get(rhs) - } - }; - } - - // OperandLiteralXxx - - // character > "xxx"_c - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_THAN, - left_expression_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - right_expression_type::value - } - }; - } - - // "xxx"_c > character - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs > lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_THAN, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - left_expression_type::value, - type_or_dispatched_type::get(rhs) - } - }; - } - - // integral > "xxx"_x - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_THAN, - left_expression_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - right_expression_type::value - } - }; - } - - // "xxx"_x > integral - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs > lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_THAN, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - left_expression_type::value, - type_or_dispatched_type::get(rhs) - } - }; - } - - // floating_point > "xxx"_x - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_THAN, - left_expression_type, - typename right_expression_type::value_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - right_expression_type::value, - right_expression_type::epsilon - } - }; - } - - // "xxx"_x > floating_point - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs > lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_THAN, - typename left_expression_type::value_type, - right_expression_type, - typename left_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - left_expression_type::value, - type_or_dispatched_type::get(rhs), - left_expression_type::epsilon - } - }; - } - - // ? > "xxx"_auto - template< - typename L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - // rebind-able type - and requires - { - typename lazy_expression_type_t, R>::template rebind< - lazy_expression_type_t, L> - >; - } - [[nodiscard]] constexpr auto operator>(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - // forward - // lhs > dispatched_expression{} - return lhs > dispatched_expression, dispatcher_type>{}; - } - - // "xxx"_auto > ? - template< - constrain_satisfied_type_or_dispatched_type_t L, - typename R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - // rebind-able type - and requires - { - typename lazy_expression_type_t, L>::template rebind< - lazy_expression_type_t, R> - >; - } - [[nodiscard]] constexpr auto operator>([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs > lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - // forward - // lhs > dispatched_expression{} - return dispatched_expression, dispatcher_type>{} > rhs; - } - - // ============================================ - // operator>= - // ============================================ - - // OperandValue - - // floating_point >= value{...} - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>=(const L& lhs, const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_EQUAL, - left_expression_type, - typename right_expression_type::value_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - type_or_dispatched_type::get(rhs).value(), - std::numeric_limits::epsilon() - } - }; - } - - // value{...} >= floating_point - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>=(const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs >= lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_EQUAL, - typename left_expression_type::value_type, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs).value(), - type_or_dispatched_type::get(rhs), - std::numeric_limits::epsilon() - } - }; - } - - // not(floating_point) >= value{...} - template< - constrain_against_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>=(const L& lhs, const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_EQUAL, - left_expression_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - type_or_dispatched_type::get(rhs).value() - } - }; - } - - // value{...} >= not(floating_point) - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_against_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>=(const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs >= lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_EQUAL, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs).value(), - type_or_dispatched_type::get(rhs) - } - }; - } - - // OperandLiteralXxx - - // character >= "xxx"_c - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_EQUAL, - left_expression_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - right_expression_type::value - } - }; - } - - // "xxx"_c >= character - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs >= lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_EQUAL, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - left_expression_type::value, - type_or_dispatched_type::get(rhs) - } - }; - } - - // integral >= "xxx"_x - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_EQUAL, - left_expression_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - right_expression_type::value - } - }; - } - - // "xxx"_x >= integral - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs >= lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_EQUAL, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - left_expression_type::value, - type_or_dispatched_type::get(rhs) - } - }; - } - - // floating_point >= "xxx"_x - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_EQUAL, - left_expression_type, typename - right_expression_type::value_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - right_expression_type::value, - right_expression_type::epsilon - } - }; - } - - // "xxx"_x >= floating_point - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator>=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs >= lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::GREATER_EQUAL, - typename left_expression_type::value_type, - right_expression_type, - typename left_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - left_expression_type::value, - type_or_dispatched_type::get(rhs), - left_expression_type::epsilon - } - }; - } - - // ? >= "xxx"_auto - template< - typename L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - // rebind-able type - and requires - { - typename lazy_expression_type_t, R>::template rebind< - lazy_expression_type_t, L> - >; - } - [[nodiscard]] constexpr auto operator>=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - // forward - // lhs >= dispatched_expression{} - return lhs >= dispatched_expression, dispatcher_type>{}; - } - - // "xxx"_auto >= ? - template< - constrain_satisfied_type_or_dispatched_type_t L, - typename R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - // rebind-able type - and requires - { - typename lazy_expression_type_t, L>::template rebind< - lazy_expression_type_t, R> - >; - } - [[nodiscard]] constexpr auto operator>=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs >= lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - // forward - // lhs >= dispatched_expression{} - return dispatched_expression, dispatcher_type>{} >= rhs; - } - - // ============================================ - // operator< - // ============================================ - - // OperandValue - - // floating_point < value{...} - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<(const L& lhs, const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_THAN, - left_expression_type, - typename right_expression_type::value_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - type_or_dispatched_type::get(rhs).value(), - std::numeric_limits::epsilon() - } - }; - } - - // value{...} < floating_point - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<(const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs < lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_THAN, - typename left_expression_type::value_type, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs).value(), - type_or_dispatched_type::get(rhs), - std::numeric_limits::epsilon() - } - }; - } - - // not(floating_point) < value{...} - template< - constrain_against_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<(const L& lhs, const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_THAN, - left_expression_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - type_or_dispatched_type::get(rhs).value() - } - }; - } - - // value{...} < not(floating_point) - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_against_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<(const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs < lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_THAN, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs).value(), - type_or_dispatched_type::get(rhs) - } - }; - } - - // OperandLiteralXxx - - // character < "xxx"_c - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_THAN, - left_expression_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - right_expression_type::value - } - }; - } - - // "xxx"_c < character - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs < lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_THAN, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - left_expression_type::value, - type_or_dispatched_type::get(rhs) - } - }; - } - - // integral < "xxx"_x - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_THAN, - left_expression_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - right_expression_type::value - } - }; - } - - // "xxx"_x < integral - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs < lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_THAN, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - left_expression_type::value, - type_or_dispatched_type::get(rhs) - } - }; - } - - // floating_point < "xxx"_x - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_THAN, - left_expression_type, - typename right_expression_type::value_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - right_expression_type::value, - right_expression_type::epsilon - } - }; - } - - // "xxx"_x < floating_point - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs < lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_THAN, - typename left_expression_type::value_type, - right_expression_type, - typename left_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - left_expression_type::value, - type_or_dispatched_type::get(rhs), - left_expression_type::epsilon - } - }; - } - - // ? < "xxx"_auto - template< - typename L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - // rebind-able type - and requires - { - typename lazy_expression_type_t, R>::template rebind< - lazy_expression_type_t, L> - >; - } - [[nodiscard]] constexpr auto operator<(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - // forward - // lhs < dispatched_expression{} - return lhs < dispatched_expression, dispatcher_type>{}; - } - - // "xxx"_auto < ? - template< - constrain_satisfied_type_or_dispatched_type_t L, - typename R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - // rebind-able type - and requires - { - typename lazy_expression_type_t, L>::template rebind< - lazy_expression_type_t, R> - >; - } - [[nodiscard]] constexpr auto operator<([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs < lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - // forward - //dispatched_expression{} < rhs - return dispatched_expression, dispatcher_type>{} < rhs; - } - - // ============================================ - // operator<= - // ============================================ - - // OperandValue - - // floating_point <= value{...} - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<=(const L& lhs, const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_EQUAL, - left_expression_type, - typename right_expression_type::value_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - type_or_dispatched_type::get(rhs).value(), - std::numeric_limits::epsilon() - } - }; - } - - // value{...} <= floating_point - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<=(const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs <= lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_EQUAL, - typename left_expression_type::value_type, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs).value(), - type_or_dispatched_type::get(rhs), - std::numeric_limits::epsilon() - } - }; - } - - // not(floating_point) <= value{...} - template< - constrain_against_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<=(const L& lhs, const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_EQUAL, - left_expression_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - type_or_dispatched_type::get(rhs).value() - } - }; - } - - // value{...} <= not(floating_point) - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_against_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<=(const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs <= lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_EQUAL, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs).value(), - type_or_dispatched_type::get(rhs) - } - }; - } - - // OperandLiteralXxx - - // character <= "xxx"_c - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_EQUAL, - left_expression_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - right_expression_type::value - } - }; - } - - // "xxx"_c <= character - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs <= lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_EQUAL, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - left_expression_type::value, - type_or_dispatched_type::get(rhs) - } - }; - } - - // integral <= "xxx"_x - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_EQUAL, - left_expression_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - right_expression_type::value - } - }; - } - - // "xxx"_x <= integral - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs <= lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_EQUAL, - typename left_expression_type::value_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - left_expression_type::value, - type_or_dispatched_type::get(rhs) - } - }; - } - - // floating_point <= "xxx"_x - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_EQUAL, - left_expression_type, - typename right_expression_type::value_type, - typename right_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - right_expression_type::value, - right_expression_type::epsilon - } - }; - } - - // "xxx"_x <= floating_point - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator<=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs <= lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LESS_EQUAL, - typename left_expression_type::value_type, - right_expression_type, - typename left_expression_type::value_type - >, - dispatcher_type - >{ - .expression = { - left_expression_type::value, - type_or_dispatched_type::get(rhs), - left_expression_type::epsilon - } - }; - } - - // ? <= "xxx"_auto - template< - typename L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - // rebind-able type - and requires - { - typename lazy_expression_type_t, R>::template rebind< - lazy_expression_type_t, L> - >; - } - [[nodiscard]] constexpr auto operator<=(const L& lhs, [[maybe_unused]] const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - // forward - // lhs <= dispatched_expression{} - return lhs <= dispatched_expression, dispatcher_type>{}; - } - - // "xxx"_auto <= ? - template< - constrain_satisfied_type_or_dispatched_type_t L, - typename R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - // rebind-able type - and requires - { - typename lazy_expression_type_t, L>::template rebind< - lazy_expression_type_t, R> - >; - } - [[nodiscard]] constexpr auto operator<=([[maybe_unused]] const L& lhs, const R& rhs) noexcept -> auto // - { - // forward - // return rhs <= lhs; - - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - // forward - // dispatched_expression{} <= rhs - return dispatched_expression, dispatcher_type>{} <= rhs; - } - - // ============================================ - // operator and & operator or - // ============================================ - - // ? and ? - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator and(const L& lhs, const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LOGICAL_AND, - left_expression_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - type_or_dispatched_type::get(rhs) - } - }; - } - - // ? or ? - template< - constrain_satisfied_type_or_dispatched_type_t L, - constrain_satisfied_type_or_dispatched_type_t R> - // can we trust users not to (inadvertently) mess up the ADL? vvv - requires(is_dispatched_expression_v or is_dispatched_expression_v) - [[nodiscard]] constexpr auto operator or(const L& lhs, const R& rhs) noexcept -> auto // - { - using dispatcher_type = lazy_dispatcher_type_t, L, R>; - - using left_expression_type = lazy_expression_type_t, L>; - using right_expression_type = lazy_expression_type_t, R>; - - return dispatched_expression< - operands::OperandExpression< - operands::ExpressionCategory::LOGICAL_OR, - left_expression_type, - right_expression_type - >, - dispatcher_type - >{ - .expression = { - type_or_dispatched_type::get(lhs), - type_or_dispatched_type::get(rhs) - } - }; - } - } // namespace detail - - template - auto register_event(EventType&& event) noexcept -> decltype(auto) // - { - return executor::executor().on(std::forward(event)); - } - - struct expect_result - { - struct fatal {}; - - private: - template - struct fatal_location - { - std::source_location location; - - constexpr explicit(false) fatal_location(const T&, const std::source_location& l = std::source_location::current()) noexcept - : location{l} {} - }; - - public: - bool value; - - constexpr explicit expect_result(const bool v) noexcept - : value{v} {} - - template - constexpr auto operator<<(MessageType&& message) noexcept -> expect_result& // - requires requires { events::EventLog{.message = std::forward(message)}; } - { - if (not value) // - { - register_event(events::EventLog{.message = std::forward(message)}); - } - - return *this; - } - - constexpr auto operator<<(const fatal_location& location) noexcept -> expect_result& - { - if (not value) // - { - register_event(events::EventAssertionFatal{.location = location.location}); - } - - return *this; - } - }; - - template - class ExpressionDispatcher - { - public: - template - [[nodiscard]] constexpr auto operator%(Lhs&& lhs) const noexcept -> detail::dispatched_expression // - { - return {.expression = std::forward(lhs)}; - } - }; - - class DispatcherThat final : public ExpressionDispatcher - { - public: - using ExpressionDispatcher::operator%; - }; - - // fixme: more dispatched expression vvv - // ... - - class DispatcherExpect - { - public: - template - requires(is_expression_v or detail::is_dispatched_expression_v) - constexpr auto operator()( - Expression&& expression, - const std::source_location& location = std::source_location::current() - ) const noexcept -> expect_result - { - if constexpr (detail::is_dispatched_expression_v) - { - // workaround if needed - // using dispatcher_type = typename Expression::dispatcher_type; - - const auto result = register_event( - events::EventAssertion{ - .expression = std::forward(expression).expression, - .location = location}); - - return expect_result{result}; - } - else // - { - return expect_result{ - register_event( - events::EventAssertion{.expression = std::forward(expression), .location = location})}; - } - } - }; - - template - class DispatcherTestBase - { - public: - using categories_type = config_type::categories_type; - - protected: - categories_type categories_; - - public: - template - constexpr auto operator=(InvocableType&& invocable) & noexcept -> DispatcherTestBase& - { - register_event(events::EventTest{ - .name = static_cast(*this).name(), - .categories = categories_, - .invocable = std::forward(invocable), - .arg = {}}); - - return *this; - } - - template - constexpr auto operator=(InvocableType&& invocable) && noexcept -> DispatcherTestBase& - { - register_event(events::EventTest{ - .name = static_cast(*this).name(), - .categories = std::move(categories_), - .invocable = std::forward(invocable), - .arg = {}}); - - return *this; - } - - private: - [[nodiscard]] constexpr auto do_move() && noexcept -> D&& { return std::move(*static_cast(this)); } - - public: - #if __cpp_multidimensional_subscript < 202110L - // fixme - #endif - template - [[nodiscard]] constexpr auto operator[](Args&&... args) && noexcept -> D&& - { - const auto process = [this](T&& t) noexcept -> void - { - using type = std::decay_t; - - if constexpr (requires { type::value; }) { categories_.emplace_back(type::value); } - else if constexpr (std::is_same_v) { categories_.emplace_back(std::forward(t)); } - else if constexpr (std::is_same_v) - { - #if __cpp_lib_containers_ranges >= 202202L - categories_.append_range(std::forward(t)); - #else - categories_.insert(categories_.end(), std::ranges::begin(std::forward(t)), std::ranges::end(std::forward(t))); - #endif - } - else { GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE("unknown type"); } - }; - - (process(std::forward(args)), ...); - - return std::move(*this).do_move(); - } - }; - - template - class DispatcherTestLiteral : public DispatcherTestBase> - { - public: - using name_type = events::name_type; - - using DispatcherTestBase::operator=; - - [[nodiscard]] constexpr static auto name() noexcept -> auto { return StringLiteral.template as(); } - }; - - class DispatcherTest : public DispatcherTestBase - { - public: - using name_type = events::name_type; - - using DispatcherTestBase::operator=; - - private: - name_type name_; - - public: - constexpr explicit DispatcherTest(const name_type name) noexcept - : name_{name} {} - - [[nodiscard]] constexpr auto name() const noexcept -> auto { return name_; } - }; - } // namespace dispatcher - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - // ========================================= - // OPERANDS - // ========================================= - - #if defined(__clang__) and __clang_major__ < 19 - // #warning "error: alias template 'value' requires template arguments; argument deduction only allowed for class templates" - #endif - template - // using value = operands::OperandValue; - [[nodiscard]] constexpr auto value(T&& v) noexcept -> auto - { - return operands::OperandValue{std::forward(v)}; - } - - // template - // using ref = operands::OperandValueRef; - template - [[nodiscard]] constexpr auto ref(T& v) noexcept -> auto - { - return operands::OperandValueRef{v}; - } - - template - [[nodiscard]] constexpr auto ref(const T& v) noexcept -> auto - { - return operands::OperandValueRef{v}; - } - - template - [[nodiscard]] constexpr auto throws(const InvocableType& invocable) noexcept -> operands::OperandThrow { return {invocable}; } - - template - [[nodiscard]] constexpr auto nothrow(const InvocableType& invocable) noexcept -> operands::OperandNoThrow { return {invocable}; } - - // ========================================= - // DISPATCHER - // ========================================= - - constexpr dispatcher::expect_result::fatal fatal{}; - - constexpr dispatcher::DispatcherThat that{}; - constexpr dispatcher::DispatcherExpect expect{}; - - // ========================================= - // CONFIG - // ========================================= - - [[nodiscard]] inline auto config() noexcept -> auto& { return executor::executor().config(); } - - // ========================================= - // TEST & SUITE - // ========================================= - - using test = dispatcher::DispatcherTest; - - template - struct suite - { - template - constexpr explicit(false) suite(InvocableType invocable) noexcept // - requires requires { +invocable; } - { - dispatcher::register_event(events::EventSuite{.name = SuiteName.template as(), .suite = +invocable}); - } - }; - - // ========================================= - // OPERATORS - // ========================================= - - namespace operators - { - namespace detail - { - template - // ReSharper disable once CppFunctionIsNotImplemented - constexpr auto is_valid_dispatched_expression(DispatchedExpression&& expression) noexcept -> void requires requires - { - static_cast(expression.expression); - }; - - template - concept dispatchable_t = - not(dispatcher::detail::is_dispatched_expression_v or dispatcher::detail::is_dispatched_expression_v); - } // namespace detail - - // a == b - template - [[nodiscard]] constexpr auto operator==(Lhs&& lhs, Rhs&& rhs) noexcept -> decltype(auto) // - requires detail::dispatchable_t and - requires { detail::is_valid_dispatched_expression(that % std::forward(lhs) == std::forward(rhs)); } // - { - return that % std::forward(lhs) == std::forward(rhs); - } - - // a != b - template - [[nodiscard]] constexpr auto operator!=(Lhs&& lhs, Rhs&& rhs) noexcept -> decltype(auto) // - requires detail::dispatchable_t and - requires { detail::is_valid_dispatched_expression(that % std::forward(lhs) != std::forward(rhs)); } // - { - return that % std::forward(lhs) != std::forward(rhs); - } - - // a > b - template - [[nodiscard]] constexpr auto operator>(Lhs&& lhs, Rhs&& rhs) noexcept -> decltype(auto) // - requires detail::dispatchable_t and - requires { detail::is_valid_dispatched_expression(that % std::forward(lhs) > std::forward(rhs)); } // - { - return that % std::forward(lhs) > std::forward(rhs); - } - - // a >= b - template - [[nodiscard]] constexpr auto operator>=(Lhs&& lhs, Rhs&& rhs) noexcept -> decltype(auto) // - requires detail::dispatchable_t and - requires { detail::is_valid_dispatched_expression(that % std::forward(lhs) >= std::forward(rhs)); } // - { - return that % std::forward(lhs) >= std::forward(rhs); - } - - // a < b - template - [[nodiscard]] constexpr auto operator<(Lhs&& lhs, Rhs&& rhs) noexcept -> decltype(auto) // - requires detail::dispatchable_t and - requires { detail::is_valid_dispatched_expression(that % std::forward(lhs) < std::forward(rhs)); } // - { - return that % std::forward(lhs) < std::forward(rhs); - } - - // a <= b - template - [[nodiscard]] constexpr auto operator<=(Lhs&& lhs, Rhs&& rhs) noexcept -> decltype(auto) // - requires detail::dispatchable_t and - requires { detail::is_valid_dispatched_expression(that % std::forward(lhs) <= std::forward(rhs)); } // - { - return that % std::forward(lhs) <= std::forward(rhs); - } - - // todo: It doesn't look like we can take over [operator and] and [operator or] :( - - // a and b - template - [[nodiscard]] constexpr auto operator and(Lhs&& lhs, Rhs&& rhs) noexcept -> decltype(auto) // - requires detail::dispatchable_t and - requires { detail::is_valid_dispatched_expression(that % std::forward(lhs) and std::forward(rhs)); } // - { - return that % std::forward(lhs) and std::forward(rhs); - } - - // a or b - template - [[nodiscard]] constexpr auto operator or(Lhs&& lhs, Rhs&& rhs) noexcept -> decltype(auto) // - requires detail::dispatchable_t and - requires { detail::is_valid_dispatched_expression(that % std::forward(lhs) or std::forward(rhs)); } // - { - return that % std::forward(lhs) or std::forward(rhs); - } - } // namespace operators - - // ========================================= - // LITERALS - // ========================================= - - namespace literals - { - template - constexpr auto operator""_test() noexcept -> dispatcher::DispatcherTestLiteral // - { - return dispatcher::DispatcherTestLiteral{}; - } - - template - [[nodiscard]] constexpr auto operator""_auto() noexcept -> operands::OperandLiteralAuto // - { - return {}; - } - - template - [[nodiscard]] constexpr auto operator""_c() noexcept -> operands::OperandLiteralCharacter // - { - return {}; - } - - template - requires requires { functional::char_list.template to_integral(); } - [[nodiscard]] constexpr auto operator""_i() noexcept - -> operands::OperandLiteralIntegral.template to_integral()> // - { - return {}; - } - - template - requires requires { functional::char_list.template to_integral(); } - [[nodiscard]] constexpr auto operator""_u() noexcept - -> operands::OperandLiteralIntegral.template to_integral()> // - { - return {}; - } - - template - requires requires { functional::char_list.template to_integral(); } - [[nodiscard]] constexpr auto operator""_l() noexcept - -> operands::OperandLiteralIntegral.template to_integral()> // - { - return {}; - } - - template - requires requires { functional::char_list.template to_integral(); } - [[nodiscard]] constexpr auto operator""_ul() noexcept - -> operands::OperandLiteralIntegral.template to_integral()> // - { - return {}; - } - - template - requires requires { functional::char_list.template to_integral(); } - [[nodiscard]] constexpr auto operator""_ll() noexcept - -> operands::OperandLiteralIntegral.template to_integral()> // - { - return {}; - } - - template - requires requires { functional::char_list.template to_integral(); } - [[nodiscard]] constexpr auto operator""_ull() noexcept - -> operands::OperandLiteralIntegral.template to_integral()> // - { - return {}; - } - - template - requires requires { functional::char_list.template to_integral(); } - [[nodiscard]] constexpr auto operator""_i8() noexcept - -> operands::OperandLiteralIntegral.template to_integral()> // - { - return {}; - } - - template - requires requires { functional::char_list.template to_integral(); } - [[nodiscard]] constexpr auto operator""_u8() noexcept - -> operands::OperandLiteralIntegral.template to_integral()> // - { - return {}; - } - - template - requires requires { functional::char_list.template to_integral(); } - [[nodiscard]] constexpr auto operator""_i16() noexcept - -> operands::OperandLiteralIntegral.template to_integral()> // - { - return {}; - } - - template - requires requires { functional::char_list.template to_integral(); } - [[nodiscard]] constexpr auto operator""_u16() noexcept - -> operands::OperandLiteralIntegral.template to_integral()> // - { - return {}; - } - - template - requires requires { functional::char_list.template to_integral(); } - [[nodiscard]] constexpr auto operator""_i32() noexcept - -> operands::OperandLiteralIntegral.template to_integral()> // - { - return {}; - } - - template - requires requires { functional::char_list.template to_integral(); } - [[nodiscard]] constexpr auto operator""_u32() noexcept - -> operands::OperandLiteralIntegral.template to_integral()> // - { - return {}; - } - - template - requires requires { functional::char_list.template to_integral(); } - [[nodiscard]] constexpr auto operator""_i64() noexcept - -> operands::OperandLiteralIntegral.template to_integral()> // - { - return {}; - } - - template - requires requires { functional::char_list.template to_integral(); } - [[nodiscard]] constexpr auto operator""_u64() noexcept - -> operands::OperandLiteralIntegral.template to_integral()> // - { - return {}; - } - - template - requires requires { functional::char_list.template to_floating_point(); } - [[nodiscard]] constexpr auto operator""_f() noexcept - -> operands::OperandLiteralFloatingPoint< - functional::char_list.template to_floating_point(), - functional::char_list.denominator_length() - > { return {}; } - - template - requires requires { functional::char_list.template to_floating_point(); } - [[nodiscard]] constexpr auto operator""_d() noexcept - -> operands::OperandLiteralFloatingPoint< - functional::char_list.template to_floating_point(), - functional::char_list.denominator_length() - > { return {}; } - - template - requires requires { functional::char_list.template to_floating_point(); } - [[nodiscard]] constexpr auto operator""_ld() noexcept - -> operands::OperandLiteralFloatingPoint< - functional::char_list.template to_floating_point(), - functional::char_list.denominator_length() - > { return {}; } - - [[nodiscard]] constexpr auto operator""_b(const char* name, const std::size_t size) noexcept -> operands::OperandIdentity::message_type // - { - return {operands::OperandIdentity::boolean{.message = {name, size}}}; - } - - [[nodiscard]] constexpr auto operator""_s(const char* name, std::size_t size) noexcept -> std::string_view { return {name, size}; } - } // namespace literals - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END -} // namespace gal::prometheus::unit_test diff --git a/src/wildcard/wildcard.ixx b/src/wildcard/wildcard.ixx index 880a8ac4..41fcc9fe 100644 --- a/src/wildcard/wildcard.ixx +++ b/src/wildcard/wildcard.ixx @@ -3,16 +3,18 @@ // This file is subject to the license terms in the LICENSE file // found in the top-level directory of this distribution. -#if GAL_PROMETHEUS_USE_MODULE -module; +#if not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED #include -export module gal.prometheus.wildcard; +export module gal.prometheus:wildcard; import std; -#else +#endif not GAL_PROMETHEUS_MODULE_FRAGMENT_DEFINED + +#if not GAL_PROMETHEUS_USE_MODULE + #pragma once #include @@ -24,33 +26,36 @@ import std; #endif -namespace gal::prometheus::wildcard +#if GAL_PROMETHEUS_INTELLISENSE_WORKING +namespace GAL_PROMETHEUS_COMPILER_MODULE_NAMESPACE_PREFIX :: wildcard +#else +GAL_PROMETHEUS_COMPILER_MODULE_NAMESPACE_EXPORT(wildcard) +#endif { - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN // https://en.cppreference.com/w/cpp/language/string_literal /** - * default wildcard: - * - * basic part: - * anything -> * - * single -> ? - * escape -> \ - * - * extend part: - * - * [] + ! -> the content in [] must appear (at least one) or not appear at all, depending on whether it has `!` - * example: - * [abc] means that one of `abc` appears at least once - * [!def] means that none of `def` appears - * - * () + | -> the content in () must appear (at least one of all the alternatives) - * example: - * (a|b|c) means that one of `a`, `b`, `c` appears - * (|d|e|f) means that one of ``, `d`, `e`, `f` appears (`` is means empty, which means empty is acceptable) - * - * - * users can specialize the template type they need, as long as the specified wildcards have operator== - */ + * default wildcard: + * + * basic part: + * anything -> * + * single -> ? + * escape -> \ + * + * extend part: + * + * [] + ! -> the content in [] must appear (at least one) or not appear at all, depending on whether it has `!` + * example: + * [abc] means that one of `abc` appears at least once + * [!def] means that none of `def` appears + * + * () + | -> the content in () must appear (at least one of all the alternatives) + * example: + * (a|b|c) means that one of `a`, `b`, `c` appears + * (|d|e|f) means that one of ``, `d`, `e`, `f` appears (`` is means empty, which means empty is acceptable) + * + * + * users can specialize the template type they need, as long as the specified wildcards have operator== + */ template struct wildcard_type; @@ -173,11 +178,10 @@ namespace gal::prometheus::wildcard value_type alt_close{U')'}; value_type alt_or{U'|'}; }; +} - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END - - namespace wildcard_detail - { +GAL_PROMETHEUS_COMPILER_MODULE_NAMESPACE_INTERNAL(wildcard) +{ enum class ResultDetail : std::uint8_t { SUCCESS, @@ -265,7 +269,7 @@ namespace gal::prometheus::wildcard template [[nodiscard]] constexpr auto is_invalid_sentinel_for(const Iterator begin, const Iterator end, const Iterator current) noexcept -> bool { - return wildcard_detail::make_invalid_sentinel_for(begin, end) == current; + return make_invalid_sentinel_for(begin, end) == current; } enum class CheckSetState : std::uint8_t @@ -373,7 +377,7 @@ namespace gal::prometheus::wildcard { if (*current != wildcard.set_open) { - return wildcard_detail::make_invalid_sentinel_for(begin, end); + return make_invalid_sentinel_for(begin, end); } // `[` is detected, then the next character should be `!` or the first option @@ -417,7 +421,7 @@ namespace gal::prometheus::wildcard std::ranges::advance(current, 1); } - return wildcard_detail::make_invalid_sentinel_for(begin, end); + return make_invalid_sentinel_for(begin, end); } template @@ -440,7 +444,7 @@ namespace gal::prometheus::wildcard { if (*pattern_current != wildcard.set_open) { - return wildcard_detail::make_match_result( + return make_match_result( ResultDetail::ERROR, sequence_begin, pattern_current @@ -465,7 +469,7 @@ namespace gal::prometheus::wildcard if (sequence_begin == sequence_end) { // just return match failed - return wildcard_detail::make_match_result( + return make_match_result( ResultDetail::MISMATCH, sequence_begin, pattern_current @@ -474,7 +478,7 @@ namespace gal::prometheus::wildcard // match succeed if (comparator(*sequence_begin, *pattern_current)) { - return wildcard_detail::make_match_result( + return make_match_result( ResultDetail::SUCCESS, sequence_begin, pattern_current @@ -495,7 +499,7 @@ namespace gal::prometheus::wildcard comparator(*sequence_begin, *pattern_current) ) { - return wildcard_detail::make_match_result( + return make_match_result( ResultDetail::MISMATCH, sequence_begin, pattern_current @@ -514,7 +518,7 @@ namespace gal::prometheus::wildcard *pattern_current == wildcard.set_close ) { - return wildcard_detail::make_match_result( + return make_match_result( ResultDetail::MISMATCH, sequence_begin, pattern_current @@ -523,7 +527,7 @@ namespace gal::prometheus::wildcard // match succeed if (comparator(*sequence_begin, *pattern_current)) { - return wildcard_detail::make_match_result( + return make_match_result( ResultDetail::SUCCESS, sequence_begin, pattern_current @@ -536,7 +540,7 @@ namespace gal::prometheus::wildcard // pattern just ended if (*pattern_current == wildcard.set_close) { - return wildcard_detail::make_match_result( + return make_match_result( ResultDetail::SUCCESS, sequence_begin, pattern_current @@ -549,7 +553,7 @@ namespace gal::prometheus::wildcard comparator(*sequence_begin, *pattern_current) ) { - return wildcard_detail::make_match_result( + return make_match_result( ResultDetail::MISMATCH, sequence_begin, pattern_current @@ -566,7 +570,7 @@ namespace gal::prometheus::wildcard std::ranges::advance(pattern_current, 1); } - return wildcard_detail::make_match_result( + return make_match_result( ResultDetail::ERROR, sequence_begin, pattern_current @@ -625,7 +629,7 @@ namespace gal::prometheus::wildcard } else if ( *current == wildcard.set_open and - wildcard_detail::check_set_exist( + check_set_exist( std::ranges::next(current), end, wildcard, @@ -633,8 +637,8 @@ namespace gal::prometheus::wildcard ) ) { - const auto result = wildcard_detail::find_set_end(std::ranges::next(current), end, wildcard, CheckSetState::NOT_OR_FIRST); - if (wildcard_detail::is_invalid_sentinel_for(std::ranges::next(current), end, result)) + const auto result = find_set_end(std::ranges::next(current), end, wildcard, CheckSetState::NOT_OR_FIRST); + if (is_invalid_sentinel_for(std::ranges::next(current), end, result)) { return false; } @@ -694,7 +698,7 @@ namespace gal::prometheus::wildcard // this character not equal to `(` if (*current != wildcard.alt_open) { - return wildcard_detail::make_invalid_sentinel_for(begin, end); + return make_invalid_sentinel_for(begin, end); } // `(` is detected, then the next character should the first option @@ -710,7 +714,7 @@ namespace gal::prometheus::wildcard } else if ( *current == wildcard.set_open and - wildcard_detail::check_set_exist( + check_set_exist( std::ranges::next(current), end, wildcard, @@ -718,10 +722,10 @@ namespace gal::prometheus::wildcard ) ) { - const auto result = wildcard_detail::find_set_end(std::ranges::next(current), end, wildcard, CheckSetState::NOT_OR_FIRST); - if (wildcard_detail::is_invalid_sentinel_for(std::ranges::next(current), end, result)) + const auto result = find_set_end(std::ranges::next(current), end, wildcard, CheckSetState::NOT_OR_FIRST); + if (is_invalid_sentinel_for(std::ranges::next(current), end, result)) { - return wildcard_detail::make_invalid_sentinel_for(begin, end); + return make_invalid_sentinel_for(begin, end); } current = std::ranges::prev(result); @@ -758,7 +762,7 @@ namespace gal::prometheus::wildcard std::ranges::advance(current, 1); } - return wildcard_detail::make_invalid_sentinel_for(begin, end); + return make_invalid_sentinel_for(begin, end); } template @@ -786,7 +790,7 @@ namespace gal::prometheus::wildcard } else if ( *current == wildcard.set_open and - wildcard_detail::check_set_exist( + check_set_exist( std::ranges::next(current), end, wildcard, @@ -794,10 +798,10 @@ namespace gal::prometheus::wildcard ) ) { - const auto result = wildcard_detail::find_set_end(std::ranges::next(current), end, wildcard, CheckSetState::NOT_OR_FIRST); - if (wildcard_detail::is_invalid_sentinel_for(std::ranges::next(current), end, result)) + const auto result = find_set_end(std::ranges::next(current), end, wildcard, CheckSetState::NOT_OR_FIRST); + if (is_invalid_sentinel_for(std::ranges::next(current), end, result)) { - return wildcard_detail::make_invalid_sentinel_for(begin, end); + return make_invalid_sentinel_for(begin, end); } current = std::ranges::prev(result); @@ -841,7 +845,7 @@ namespace gal::prometheus::wildcard std::ranges::advance(current, 1); } - return wildcard_detail::make_invalid_sentinel_for(begin, end); + return make_invalid_sentinel_for(begin, end); } template @@ -870,7 +874,7 @@ namespace gal::prometheus::wildcard ) noexcept -> match_result { // is the target sequence partial matches pattern1 - if (auto result1 = wildcard_detail::match( + if (auto result1 = match( sequence_begin, sequence_end, pattern1_begin, @@ -882,7 +886,7 @@ namespace gal::prometheus::wildcard result1) { // is the target sequence matches pattern2 - if (auto result2 = wildcard_detail::match( + if (auto result2 = match( result1.sequence, sequence_end, pattern2_begin, @@ -899,17 +903,17 @@ namespace gal::prometheus::wildcard // pattern1 and pattern2 are two connected parts if (pattern1_current == pattern2_begin) { - return wildcard_detail::make_match_result( + return make_match_result( ResultDetail::MISMATCH, sequence_begin, pattern1_end ); } - const auto sub_alt_end = wildcard_detail::find_sub_alt_end(pattern1_current, pattern2_begin, wildcard); - if (wildcard_detail::is_invalid_sentinel_for(pattern1_current, pattern2_begin, sub_alt_end)) + const auto sub_alt_end = find_sub_alt_end(pattern1_current, pattern2_begin, wildcard); + if (is_invalid_sentinel_for(pattern1_current, pattern2_begin, sub_alt_end)) { - return wildcard_detail::make_match_result( + return make_match_result( ResultDetail::ERROR, sequence_begin, pattern1_current @@ -917,7 +921,7 @@ namespace gal::prometheus::wildcard } // the current position is relatively successful, continue to compare the next position - return wildcard_detail::match_alt( + return match_alt( sequence_begin, sequence_end, pattern1_current, @@ -947,7 +951,7 @@ namespace gal::prometheus::wildcard { const auto result = partial or sequence_begin == sequence_end; - return wildcard_detail::make_match_result( + return make_match_result( // if it is a partial match or is not a valid sequence, the match is considered successful result ? ResultDetail::SUCCESS : ResultDetail::MISMATCH, sequence_begin, @@ -964,7 +968,7 @@ namespace gal::prometheus::wildcard not comparator(*sequence_begin, *pattern_begin) ) { - return wildcard_detail::make_match_result( + return make_match_result( ResultDetail::MISMATCH, sequence_begin, pattern_begin @@ -972,7 +976,7 @@ namespace gal::prometheus::wildcard } // the current position is relatively successful, continue to compare the next position - return wildcard_detail::match( + return match( std::ranges::next(sequence_begin), sequence_end, std::ranges::next(pattern_begin), @@ -987,7 +991,7 @@ namespace gal::prometheus::wildcard { // if the current position of the pattern is `*`, try to match the next position of the pattern // if the match is still successful, skip the current `*` - if (auto result = wildcard_detail::match( + if (auto result = match( sequence_begin, sequence_end, std::ranges::next(pattern_begin), @@ -1001,7 +1005,7 @@ namespace gal::prometheus::wildcard // not a valid sequence if (sequence_begin == sequence_end) { - return wildcard_detail::make_match_result( + return make_match_result( ResultDetail::MISMATCH, sequence_begin, pattern_begin @@ -1009,7 +1013,7 @@ namespace gal::prometheus::wildcard } // if the match is not successful, skip the current position of sequence - return wildcard_detail::match( + return match( std::ranges::next(sequence_begin), sequence_end, pattern_begin, @@ -1027,7 +1031,7 @@ namespace gal::prometheus::wildcard // not a valid sequence if (sequence_begin == sequence_end) { - return wildcard_detail::make_match_result( + return make_match_result( ResultDetail::MISMATCH, sequence_begin, pattern_begin @@ -1035,7 +1039,7 @@ namespace gal::prometheus::wildcard } // try to match the next position of the pattern and sequence - return wildcard_detail::match( + return match( std::ranges::next(sequence_begin), sequence_end, std::ranges::next(pattern_begin), @@ -1049,7 +1053,7 @@ namespace gal::prometheus::wildcard if (*pattern_begin == wildcard.escape) { // match the next position of the pattern - return wildcard_detail::match( + return match( sequence_begin, sequence_end, std::ranges::next(pattern_begin), @@ -1063,7 +1067,7 @@ namespace gal::prometheus::wildcard if ( *pattern_begin == wildcard.set_open and - wildcard_detail::check_set_exist( + check_set_exist( std::ranges::next(pattern_begin), pattern_end, wildcard, @@ -1072,7 +1076,7 @@ namespace gal::prometheus::wildcard ) { // if the nested set does not match successfully, the result will be returned directly (the match failed) - if (auto result = wildcard_detail::match_set( + if (auto result = match_set( sequence_begin, sequence_end, std::ranges::next(pattern_begin), @@ -1083,13 +1087,13 @@ namespace gal::prometheus::wildcard ); not result) { return result; } - const auto pattern_set_end = wildcard_detail::find_set_end(std::ranges::next(pattern_begin), pattern_end, wildcard, CheckSetState::NOT_OR_FIRST); - if (wildcard_detail::is_invalid_sentinel_for(std::ranges::next(pattern_begin), pattern_end, pattern_set_end)) + const auto pattern_set_end = find_set_end(std::ranges::next(pattern_begin), pattern_end, wildcard, CheckSetState::NOT_OR_FIRST); + if (is_invalid_sentinel_for(std::ranges::next(pattern_begin), pattern_end, pattern_set_end)) [[unlikely]] { // assert? - return wildcard_detail::make_match_result( + return make_match_result( ResultDetail::ERROR, sequence_begin, pattern_begin @@ -1097,7 +1101,7 @@ namespace gal::prometheus::wildcard } // after the match is successful, skip this nested set to continue matching - return wildcard_detail::match( + return match( std::ranges::next(sequence_begin), sequence_end, pattern_set_end, @@ -1110,7 +1114,7 @@ namespace gal::prometheus::wildcard if ( *pattern_begin == wildcard.alt_open and - wildcard_detail::check_alt_exist( + check_alt_exist( std::ranges::next(pattern_begin), pattern_end, wildcard, @@ -1119,38 +1123,38 @@ namespace gal::prometheus::wildcard ) ) { - const auto pattern_alt_end = wildcard_detail::find_alt_end( + const auto pattern_alt_end = find_alt_end( std::ranges::next(pattern_begin), pattern_end, wildcard, CheckAltState::NEXT, 1 ); - if (wildcard_detail::is_invalid_sentinel_for(std::ranges::next(pattern_begin), pattern_end, pattern_alt_end)) + if (is_invalid_sentinel_for(std::ranges::next(pattern_begin), pattern_end, pattern_alt_end)) [[unlikely]] { // assert? - return wildcard_detail::make_match_result( + return make_match_result( ResultDetail::ERROR, sequence_begin, pattern_begin ); } - const auto pattern_sub_alt_end = wildcard_detail::find_sub_alt_end(std::ranges::next(pattern_begin), pattern_alt_end, wildcard); - if (wildcard_detail::is_invalid_sentinel_for(std::ranges::next(pattern_begin), pattern_alt_end, pattern_sub_alt_end)) + const auto pattern_sub_alt_end = find_sub_alt_end(std::ranges::next(pattern_begin), pattern_alt_end, wildcard); + if (is_invalid_sentinel_for(std::ranges::next(pattern_begin), pattern_alt_end, pattern_sub_alt_end)) { // assert? - return wildcard_detail::make_match_result( + return make_match_result( ResultDetail::ERROR, sequence_begin, pattern_begin ); } - return wildcard_detail::match_alt( + return match_alt( sequence_begin, sequence_end, std::ranges::next(pattern_begin), @@ -1169,7 +1173,7 @@ namespace gal::prometheus::wildcard // the match not succeed here not comparator(*sequence_begin, *pattern_begin)) { - return wildcard_detail::make_match_result( + return make_match_result( ResultDetail::MISMATCH, sequence_begin, pattern_begin @@ -1177,7 +1181,7 @@ namespace gal::prometheus::wildcard } // the current position is relatively successful, continue to compare the next position - return wildcard_detail::match( + return match( std::ranges::next(sequence_begin), sequence_end, std::ranges::next(pattern_begin), @@ -1189,8 +1193,12 @@ namespace gal::prometheus::wildcard } } - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_BEGIN - +#if GAL_PROMETHEUS_INTELLISENSE_WORKING +namespace GAL_PROMETHEUS_COMPILER_MODULE_NAMESPACE_PREFIX :: wildcard +#else +GAL_PROMETHEUS_COMPILER_MODULE_NAMESPACE_EXPORT(wildcard) +#endif +{ template constexpr auto match( const SequenceIterator sequence_begin, @@ -1200,14 +1208,14 @@ namespace gal::prometheus::wildcard const wildcard_type())>>& wildcard = {}, const Comparator& comparator = {} // ) noexcept - -> wildcard_detail::full_match_result + -> GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::full_match_result { - return wildcard_detail::make_full_match_result( + return GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::make_full_match_result( sequence_begin, sequence_end, pattern_begin, pattern_end, - wildcard_detail::match( + GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::match( sequence_begin, sequence_end, pattern_begin, @@ -1226,7 +1234,7 @@ namespace gal::prometheus::wildcard const wildcard_type())>>& wildcard = {}, const Comparator& comparator = {} // ) noexcept - -> wildcard_detail::full_match_result())), PatternIterator> + -> GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::full_match_result())), PatternIterator> { return wildcard::match( std::ranges::begin(sequence), @@ -1246,7 +1254,7 @@ namespace gal::prometheus::wildcard const wildcard_type>& wildcard = {}, const Comparator& comparator = {} // ) noexcept - -> wildcard_detail::full_match_result()))> + -> GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::full_match_result()))> { return wildcard::match( sequence_begin, @@ -1265,7 +1273,7 @@ namespace gal::prometheus::wildcard const wildcard_type>& wildcard = {}, const Comparator& comparator = {} // ) noexcept - -> wildcard_detail::full_match_result< + -> GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::full_match_result< decltype(std::ranges::cbegin(std::declval())), decltype(std::ranges::cbegin(std::declval())) > @@ -1337,14 +1345,14 @@ namespace gal::prometheus::wildcard constexpr auto operator()( SequenceIterator sequence_begin, SequenceIterator sequence_end // - ) const noexcept -> wildcard_detail::full_match_result + ) const noexcept -> GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::full_match_result { return prometheus::wildcard::match(sequence_begin, sequence_end, borrow_pattern_begin_, borrow_pattern_end_, current_wildcard_, current_comparator_); } template constexpr auto operator()(const Sequence& sequence) const noexcept - -> wildcard_detail::full_match_result())), const_iterator> + -> GAL_PROMETHEUS_COMPILER_MODULE_INTERNAL::full_match_result())), const_iterator> { return prometheus::wildcard::match(sequence, borrow_pattern_begin_, borrow_pattern_end_, current_wildcard_, current_comparator_); } @@ -1437,6 +1445,4 @@ namespace gal::prometheus::wildcard return make_wildcard_matcher(std::basic_string_view{str, size + 1}); } } // namespace literals - - GAL_PROMETHEUS_COMPILER_MODULE_EXPORT_END } diff --git a/unit_test/CMakeLists.txt b/unit_test/CMakeLists.txt index 1a638437..df27a1e8 100644 --- a/unit_test/CMakeLists.txt +++ b/unit_test/CMakeLists.txt @@ -7,27 +7,53 @@ add_executable( ${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/src/main.cpp + + # ================================= + # META + # ================================= + + ${PROJECT_SOURCE_DIR}/src/meta/enumeration.cpp + ${PROJECT_SOURCE_DIR}/src/meta/member.cpp + ${PROJECT_SOURCE_DIR}/src/meta/dimension.cpp + # ================================= + # FUNCTIONAL + # ================================= + + ${PROJECT_SOURCE_DIR}/src/functional/aligned_union.cpp + ${PROJECT_SOURCE_DIR}/src/functional/function_ref.cpp + # ================================= # CHARS # ================================= - - ${PROJECT_SOURCE_DIR}/src/chars/ascii.cpp - ${PROJECT_SOURCE_DIR}/src/chars/utf8.cpp - ${PROJECT_SOURCE_DIR}/src/chars/utf16.cpp - ${PROJECT_SOURCE_DIR}/src/chars/utf32.cpp + + ${PROJECT_SOURCE_DIR}/src/chars/scalar.cpp + ${PROJECT_SOURCE_DIR}/src/chars/icelake.cpp # ================================= - # META + # CONCURRENCY # ================================= - - ${PROJECT_SOURCE_DIR}/src/meta/enumeration.cpp - + + ${PROJECT_SOURCE_DIR}/src/concurrency/queue.cpp + # ================================= - # WILDCARD + # COROUTINE # ================================= - - ${PROJECT_SOURCE_DIR}/src/wildcard/wildcard.cpp + + ${PROJECT_SOURCE_DIR}/src/coroutine/task.cpp + ${PROJECT_SOURCE_DIR}/src/coroutine/generator.cpp + + # ========================= + # STRING + # ========================= + + ${PROJECT_SOURCE_DIR}/src/string/string_pool.cpp + + ## ================================= + ## WILDCARD + ## ================================= + # + #${PROJECT_SOURCE_DIR}/src/wildcard/wildcard.cpp ) target_compile_features( @@ -38,113 +64,17 @@ target_compile_features( target_link_libraries( ${PROJECT_NAME} - PUBLIC + PRIVATE prometheus ) add_test( NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) # assets path set(ASSETS_PATH_PIC ${CMAKE_SOURCE_DIR}/assets/pic.jpg) - -#find_package(Vulkan) -# -#if (Vulkan_FOUND) -# add_executable( -# ${PROJECT_NAME}-gui-vulkan -# -# ${PROJECT_SOURCE_DIR}/src/gui/main_vulkan.cpp -# ) -# -# # glfw: clone from github -# CPMAddPackage( -# NAME glfw -# GIT_TAG 3.4 -# GITHUB_REPOSITORY "glfw/glfw" -# OPTIONS "BUILD_SHARED_LIBS OFF" "GLFW_BUILD_EXAMPLES OFF" "GLFW_BUILD_TESTS OFF" "GLFW_BUILD_DOCS OFF" "GLFW_INSTALL OFF" -# ) -# cmake_language( -# CALL -# ${PROJECT_NAME_PREFIX}cpm_install -# ${PROJECT_NAME}-gui-vulkan -# glfw -# PUBLIC -# glfw -# ) -# -# target_link_libraries(${PROJECT_NAME}-gui-vulkan PRIVATE ${Vulkan_LIBRARY}) -# target_include_directories(${PROJECT_NAME}-gui-vulkan PRIVATE ${Vulkan_INCLUDE_DIRS}) -# -# target_compile_features( -# ${PROJECT_NAME}-gui-vulkan -# PUBLIC -# cxx_std_23 -# ) -# -# target_link_libraries( -# ${PROJECT_NAME}-gui-vulkan -# PUBLIC -# prometheus -# ) -#endif (Vulkan_FOUND) - -add_executable( - ${PROJECT_NAME}-gui-dx12 - - ${PROJECT_SOURCE_DIR}/src/gui/glfw_callback_handler.cpp - ${PROJECT_SOURCE_DIR}/src/gui/backend_dx12.cpp - ${PROJECT_SOURCE_DIR}/src/gui/main_dx12.cpp -) - -target_compile_features( - ${PROJECT_NAME}-gui-dx12 - PUBLIC - cxx_std_23 -) - -target_link_libraries( - ${PROJECT_NAME}-gui-dx12 - PUBLIC - prometheus -) -link_3rd_library_stb(${PROJECT_NAME}-gui-dx12) -link_3rd_library_glfw(${PROJECT_NAME}-gui-dx12) - -target_compile_definitions( - ${PROJECT_NAME}-gui-dx12 - PUBLIC - - ASSETS_PATH_PIC="${ASSETS_PATH_PIC}" -) - -add_executable( - ${PROJECT_NAME}-gui-dx11 - - ${PROJECT_SOURCE_DIR}/src/gui/glfw_callback_handler.cpp - ${PROJECT_SOURCE_DIR}/src/gui/backend_dx11.cpp - ${PROJECT_SOURCE_DIR}/src/gui/main_dx11.cpp -) - -target_compile_features( - ${PROJECT_NAME}-gui-dx11 - PUBLIC - cxx_std_23 -) - -target_link_libraries( - ${PROJECT_NAME}-gui-dx11 - PUBLIC - prometheus -) -link_3rd_library_stb(${PROJECT_NAME}-gui-dx11) -link_3rd_library_glfw(${PROJECT_NAME}-gui-dx11) - -target_compile_definitions( - ${PROJECT_NAME}-gui-dx11 - PUBLIC - - ASSETS_PATH_PIC="${ASSETS_PATH_PIC}" -) +# DRAW +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/draw) diff --git a/unit_test/src/chars/ascii.cpp b/unit_test/src/chars/ascii.cpp deleted file mode 100644 index 9c4c007d..00000000 --- a/unit_test/src/chars/ascii.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include - -#if GAL_PROMETHEUS_USE_MODULE -import std; -import gal.prometheus; -#else -#include - -#include -#include -#include -#endif - -namespace -{ - using namespace gal::prometheus; - - [[nodiscard]] auto make_source(auto generator, const auto size) -> std::basic_string - { - std::basic_string source{}; - source.resize(size); - std::ranges::generate_n(source.begin(), size, generator); - - return source; - } - - GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.ascii"> _ = [] - { - using namespace unit_test; - using namespace literals; - - const auto old_level = std::exchange(config().output_level, OutputLevel::NONE); - - numeric::Random random; - const auto generator = [&random]() mutable - { - return static_cast(random.get() & std::numeric_limits::max()); - }; - - constexpr std::size_t trials = 1000; - for (std::size_t i = 0; i < trials; ++i) - { - "to_ascii"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid ascii string"_b) << fatal; - expect(chars::length(source) == value(source.size())) << fatal; - - const auto dest = chars::convert(source); - expect((dest == ref(source)) == "valid ascii string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect((dest_a == ref(source)) == "valid ascii string"_b) << fatal; - }; - - "to_utf8_char"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid ascii string"_b) << fatal; - expect(chars::length(source) == value(source.size())) << fatal; - expect((chars::convert(source) == source) == "valid utf8_char string"_b) << fatal; - expect((chars::convert(source) == source) == "valid utf8_char string"_b) << fatal; - }; - - "to_utf8"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid ascii string"_b) << fatal; - expect(chars::length(source) == value(source.size())) << fatal; - expect(chars::validate(chars::convert(source)) == "valid utf8 string"_b) << fatal; - expect(chars::validate(chars::convert(source)) == "valid utf8 string"_b) << fatal; - }; - - "to_utf16_le"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid ascii string"_b) << fatal; - expect(chars::length(source) == value(source.size())) << fatal; - expect(chars::validate(chars::convert(source)) == "valid utf16_le string"_b) << fatal; - expect(chars::validate(chars::convert(source)) == "valid utf16_le string"_b) << fatal; - }; - - "to_utf16_be"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid ascii string"_b) << fatal; - expect(chars::length(source) == value(source.size())) << fatal; - expect(chars::validate(chars::convert(source)) == "valid utf16_be string"_b) << fatal; - expect(chars::validate(chars::convert(source)) == "valid utf16_be string"_b) << fatal; - }; - - "to_utf32"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid ascii string"_b) << fatal; - expect(chars::length(source) == value(source.size())) << fatal; - expect(chars::validate(chars::convert(source)) == "valid utf32 string"_b) << fatal; - expect(chars::validate(chars::convert(source)) == "valid utf32 string"_b) << fatal; - }; - } - - config().output_level = old_level; - }; -} diff --git a/unit_test/src/chars/gen.hpp b/unit_test/src/chars/gen.hpp new file mode 100644 index 00000000..5a685776 --- /dev/null +++ b/unit_test/src/chars/gen.hpp @@ -0,0 +1,1484 @@ +#pragma once + +// numeric::random +#include +// i18n::range +#include +// unit_test +#include +// chars +#include + +using namespace gal::prometheus; // NOLINT(clang-diagnostic-header-hygiene) + +namespace gen_detail +{ + // ===================================================== + + [[nodiscard]] inline auto ranges_ascii() noexcept -> auto + { + return i18n::RangeBuilder{}.ascii().range(); + } + + [[nodiscard]] inline auto ranges_latin() noexcept -> auto + { + return i18n::RangeBuilder{}.latin().range(); + } + + [[nodiscard]] inline auto ranges_all() noexcept -> auto + { + return i18n::RangeBuilder{}.latin().greek().korean().japanese().simplified_chinese_common().range(); + } + + template + [[nodiscard]] auto generate_string(const auto& generator, const std::size_t length) noexcept -> std::basic_string + { + std::basic_string result{}; + result.reserve(length); + + auto remaining = length - 1; + while (remaining -= generator(result, remaining)) {} + + return result; + } + + // ===================================================== + // LATIN + + template + [[nodiscard]] auto make_random_latin_string(const std::size_t min_length, const std::size_t max_length) noexcept -> std::basic_string + { + using namespace gal::prometheus::numeric; + + using char_type = Char; + + Random random{}; + const auto& ranges = []() noexcept + { + if constexpr (AsciiOnly) + { + return ranges_ascii(); + } + else + { + return ranges_latin(); + } + }(); + + const auto g = [&random, &ranges](auto& dest, [[maybe_unused]] const auto remaining) noexcept -> std::size_t + { + const auto& [from, to] = ranges[random.get(0, ranges.size() - 1)]; + + const auto v = random.get(from, to); + + { + const auto v1 = static_cast(v); + dest.push_back(v1); + return 1; + } + }; + + const auto length = random.get(min_length, max_length); + return gen_detail::generate_string(g, length); + } + + // ===================================================== + // UTF8 + + template + [[nodiscard]] auto make_random_utf8_string(const std::size_t min_length, const std::size_t max_length) noexcept -> std::basic_string + { + using namespace gal::prometheus::numeric; + + using char_type = Char; + + Random random{}; + const auto& ranges = []() noexcept + { + if constexpr (AsciiOnly) + { + return ranges_ascii(); + } + else + { + return ranges_all(); + } + }(); + + const auto g = [&random, ranges](auto& dest, [[maybe_unused]] const auto remaining) noexcept -> std::size_t + { + const auto& [from, to] = ranges[random.get(0, ranges.size() - 1)]; + + std::size_t max_try = 3; + while (max_try--) + { + auto v = random.get(from, to); + + if (v < 0x80) + { + const auto v1 = static_cast(v); + dest.push_back(v1); + + return 1; + } + + if constexpr (not AsciiOnly) + { + if (remaining >= 2 and v < 0x800) + { + const auto v1 = static_cast(0xc0 | ((v >> 6) & 0x1f)); + const auto v2 = static_cast(0x80 | (v & 0x3f)); + dest.push_back(v1); + dest.push_back(v2); + + return 2; + } + + if (v >= 0xd800 and v <= 0xdfff) + { + // skip surrogate pairs + v += (0xdfff - 0xd800 + 1); + } + + if (remaining >= 3 and v < 0x1'0000) + { + const auto v1 = static_cast(0xe0 | ((v >> 12) & 0xf)); + const auto v2 = static_cast(0x80 | ((v >> 6) & 0x3f)); + const auto v3 = static_cast(0x80 | (v & 0x3f)); + dest.push_back(v1); + dest.push_back(v2); + dest.push_back(v3); + + return 3; + } + + if (remaining >= 4) + { + const auto v1 = static_cast(0xf0 | ((v >> 18) & 0x7)); + const auto v2 = static_cast(0x80 | ((v >> 12) & 0x3f)); + const auto v3 = static_cast(0x80 | ((v >> 6) & 0x3f)); + const auto v4 = static_cast(0x80 | (v & 0x3f)); + dest.push_back(v1); + dest.push_back(v2); + dest.push_back(v3); + dest.push_back(v4); + + return 4; + } + } + } + + if constexpr (std::is_same_v) + { + dest.push_back('?'); + } + else + { + dest.push_back(u8'?'); + } + + return 1; + }; + + const auto length = random.get(min_length, max_length); + return gen_detail::generate_string(g, length); + } + + // ===================================================== + // UTF16 + + template + [[nodiscard]] auto make_random_utf16_string(const std::size_t min_length, const std::size_t max_length) noexcept -> std::basic_string + { + using namespace gal::prometheus::numeric; + + using char_type = char16_t; + + Random random{}; + const auto& ranges = []() noexcept + { + if constexpr (AsciiOnly) + { + return ranges_ascii(); + } + else + { + return ranges_all(); + } + }(); + + const auto g = [&random, &ranges](auto& dest, [[maybe_unused]] const auto remaining) noexcept -> std::size_t + { + const auto& [from, to] = ranges[random.get(0, ranges.size() - 1)]; + + std::size_t max_try = 3; + while (max_try--) + { + auto v = random.get(from, to); + + if (v < 0x80) + { + const auto v1 = static_cast(v); + if constexpr (Little) + { + dest.push_back(v1); + } + else + { + const auto v1_swap = std::byteswap(v1); + dest.push_back(v1_swap); + } + + return 1; + } + + if constexpr (not AsciiOnly) + { + if (v >= 0xd800 and v <= 0xdfff) + { + // skip surrogate pairs + v += (0xdfff - 0xd800 + 1); + } + + if (v < 0xffff) + { + const auto v1 = static_cast(v); + if constexpr (Little) + { + dest.push_back(v1); + } + else + { + const auto v1_swap = std::byteswap(v1); + dest.push_back(v1_swap); + } + + return 1; + } + + if (remaining >= 2) + { + v -= 0x1'0000; + const auto high_surrogate = static_cast(0xd800 | ((v >> 10) & 0x3ff)); + const auto low_surrogate = static_cast(0xdc00 | (v & 0x3ff)); + + if constexpr (Little) + { + dest.push_back(high_surrogate); + dest.push_back(low_surrogate); + } + else + { + const auto high_surrogate_swap = std::byteswap(high_surrogate); + const auto low_surrogate_swap = std::byteswap(low_surrogate); + dest.push_back(high_surrogate_swap); + dest.push_back(low_surrogate_swap); + } + + return 2; + } + } + } + + dest.push_back(u'?'); + return 1; + }; + + const auto length = random.get(min_length, max_length); + return gen_detail::generate_string(g, length); + } + + // ===================================================== + // UTF32 + + template + [[nodiscard]] auto make_random_utf32_string(const std::size_t min_length, const std::size_t max_length) noexcept -> std::basic_string + { + using namespace gal::prometheus::numeric; + + using char_type = char32_t; + + Random random{}; + const auto& ranges = []() noexcept + { + if constexpr (AsciiOnly) + { + return ranges_ascii(); + } + else + { + return ranges_all(); + } + }(); + + const auto g = [&random, &ranges](auto& dest, [[maybe_unused]] const auto remaining) noexcept -> std::size_t + { + const auto& [from, to] = ranges[random.get(0, ranges.size() - 1)]; + + auto v = random.get(from, to); + + if (v < 0x80) + { + const auto v1 = static_cast(v); + dest.push_back(v1); + return 1; + } + + if constexpr (not AsciiOnly) + { + if (v >= 0xd800 and v <= 0xdfff) + { + // skip surrogate pairs + v += (0xdfff - 0xd800 + 1); + } + + const auto v1 = static_cast(v); + dest.push_back(v1); + + return 1; + } + else + { + GAL_PROMETHEUS_ERROR_UNREACHABLE(); + } + }; + + const auto length = random.get(min_length, max_length); + return gen_detail::generate_string(g, length); + } +} + +// ============================== +// LATIN + +[[nodiscard]] inline auto make_random_latin_string(const std::size_t min_length = 4096, const std::size_t max_length = 65535) noexcept -> std::basic_string +{ + return gen_detail::make_random_latin_string(min_length, max_length); +} + +[[nodiscard]] inline auto make_random_latin_string_ascii_only(const std::size_t min_length = 4096, const std::size_t max_length = 65535) noexcept -> std::basic_string +{ + return gen_detail::make_random_latin_string(min_length, max_length); +} + +// ============================== +// UTF8 + +[[nodiscard]] inline auto make_random_utf8_char_string(const std::size_t min_length = 4096, const std::size_t max_length = 65535) noexcept -> std::basic_string +{ + return gen_detail::make_random_utf8_string(min_length, max_length); +} + +[[nodiscard]] inline auto make_random_utf8_char_string_ascii_only(const std::size_t min_length = 4096, const std::size_t max_length = 65535) noexcept -> std::basic_string +{ + return gen_detail::make_random_utf8_string(min_length, max_length); +} + +[[nodiscard]] inline auto make_random_utf8_string(const std::size_t min_length = 4096, const std::size_t max_length = 65535) noexcept -> std::basic_string +{ + return gen_detail::make_random_utf8_string(min_length, max_length); +} + +[[nodiscard]] inline auto make_random_utf8_string_ascii_only(const std::size_t min_length = 4096, const std::size_t max_length = 65535) noexcept -> std::basic_string +{ + return gen_detail::make_random_utf8_string(min_length, max_length); +} + +// ============================== +// UTF16 + +[[nodiscard]] inline auto make_random_utf16_le_string(const std::size_t min_length = 4096, const std::size_t max_length = 65535) noexcept -> std::basic_string +{ + return gen_detail::make_random_utf16_string(min_length, max_length); +} + +[[nodiscard]] inline auto make_random_utf16_be_string(const std::size_t min_length = 4096, const std::size_t max_length = 65535) noexcept -> std::basic_string +{ + return gen_detail::make_random_utf16_string(min_length, max_length); +} + +[[nodiscard]] inline auto make_random_utf16_le_string_ascii_only(const std::size_t min_length = 4096, const std::size_t max_length = 65535) noexcept -> std::basic_string +{ + return gen_detail::make_random_utf16_string(min_length, max_length); +} + +[[nodiscard]] inline auto make_random_utf16_be_string_ascii_only(const std::size_t min_length = 4096, const std::size_t max_length = 65535) noexcept -> std::basic_string +{ + return gen_detail::make_random_utf16_string(min_length, max_length); +} + +// ============================== +// UTF32 + +[[nodiscard]] inline auto make_random_utf32_string(const std::size_t min_length = 4096, const std::size_t max_length = 65535) noexcept -> std::basic_string +{ + return gen_detail::make_random_utf32_string(min_length, max_length); +} + +[[nodiscard]] inline auto make_random_utf32_string_ascii_only(const std::size_t min_length = 4096, const std::size_t max_length = 65535) noexcept -> std::basic_string +{ + return gen_detail::make_random_utf32_string(min_length, max_length); +} + +using chars::CharsType; +using chars::ErrorCode; +using chars::input_type_of; +using chars::output_type_of; + +template< + CharsType InputType, + CharsType OutputType, + typename Impl, + // LATIN ==> PURE ASCII ONLY + bool ValidateSource = true +> +constexpr auto make_test( + const input_type_of source +) noexcept -> void +{ + using namespace gal::prometheus::unit_test; + + using output_type = output_type_of; + using output_char_type = typename output_type::value_type; + using output_string_type = std::basic_string; + + if constexpr (ValidateSource) + { + expect(Impl::template validate(source) == "valid source string"_b) << fatal; + } + + const auto source_length = source.size(); + const auto output_length = Impl::template length(source); + + { + output_string_type dest{}; + dest.resize(output_length); + + const auto convert_result = Impl::template convert(dest.data(), source); + expect(convert_result.has_error() != "valid source string"_b) << fatal; + expect(convert_result.input == value(source_length)) << fatal; + if constexpr (requires { convert_result.output; }) + { + expect(convert_result.output == value(output_length)) << fatal; + } + + const auto validate_output_result = Impl::template validate(dest); + expect(validate_output_result.has_error() != "valid output string"_b) << fatal; + expect(validate_output_result.input == value(output_length)) << fatal; + + const auto result = Impl::template convert(source); + expect(dest == ref(result)) << fatal; + } + + if constexpr (ValidateSource) + { + output_string_type dest{}; + dest.resize(output_length); + + const auto convert_result = Impl::template convert(dest.data(), source); + if constexpr (requires { convert_result.error; }) + { + expect(convert_result.has_error() != "valid output string"_b) << fatal; + expect(convert_result.input == value(output_length)) << fatal; + } + if constexpr (requires { convert_result.output; }) + { + expect(convert_result.output == value(output_length)) << fatal; + } + + const auto validate_output_result = Impl::template validate(dest); + expect(validate_output_result.has_error() != "valid output string"_b) << fatal; + expect(validate_output_result.input == value(output_length)) << fatal; + + const auto result = Impl::template convert(source); + expect(dest == ref(result)) << fatal; + } +} + +template< + CharsType InputType, + CharsType OutputType, + typename Impl, + // LATIN ==> PURE ASCII ONLY + bool ValidateSourceOnly = false +> +constexpr auto make_test( + const input_type_of source, + const ErrorCode expected_error, + const std::size_t expected_validate_input +) noexcept -> void +{ + using namespace gal::prometheus::unit_test; + + using output_type = output_type_of; + using output_char_type = typename output_type::value_type; + using output_string_type = std::basic_string; + + const auto output_length = Impl::template length(source); + + const auto validate_source_result = Impl::template validate(source); + expect(validate_source_result.has_error() == "invalid source string"_b) << fatal; + expect(validate_source_result.error == value(expected_error)) << fatal; + expect(validate_source_result.input == value(expected_validate_input)) << fatal; + + if constexpr (not ValidateSourceOnly) + { + output_string_type dest{}; + dest.resize(output_length); + + const auto convert_result = Impl::template convert(dest.data(), source); + expect(convert_result.has_error() == "invalid source string"_b) << fatal; + expect(convert_result.error == value(expected_error)) << fatal; + expect(convert_result.input == value(expected_validate_input)) << fatal; + + const auto validate_output_result = Impl::template validate(dest); + expect(validate_output_result.has_error() != "valid output string"_b) << fatal; + + const auto result = Impl::template convert(source); + expect(dest == ref(result)) << fatal; + } +} + +template +constexpr auto make_test_detect_encoding() noexcept -> void +{ + using namespace gal::prometheus::unit_test; + + "UTF8"_test = [] + { + constexpr std::uint8_t source_u8[] + { + // 你好,世界! + 0xe4, 0xbd, 0xa0, 0xe5, 0xa5, 0xbd, 0x2c, 0xe4, 0xb8, 0x96, 0xe7, 0x95, 0x8c, 0x21, 0x20, + // Привет! + 0xd0, 0x9f, 0xd1, 0x80, 0xd0, 0xb8, 0xd0, 0xb2, 0xd0, 0xb5, 0xd1, 0x82, 0x21, 0x20, + // 😀 + 0xf0, 0x9f, 0x98, 0x80, 0x20, + // Hello, world! + 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x20, + // 我 am utf-8 + 0xe6, 0x88, 0x91, 0x20, 0x61, 0x6d, 0x20, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x20, + // 你好 🌍! + 0xe4, 0xbd, 0xa0, 0xe5, 0xa5, 0xbd, 0x20, 0xf0, 0x9f, 0x8c, 0x8d, 0x21, 0x20, + // 123 + 0x31, 0x32, 0x33 + }; + constexpr auto result = chars::EncodingType::UTF8; + + constexpr auto source_length = std::ranges::size(source_u8); + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + + const auto encoding = Impl::encoding_of(source); + expect(encoding == value(result)); + }; + + "UTF16"_test = [] + { + constexpr std::uint8_t source_u8[] + { + 0xf1, 0x10, 0x10, 0x10, + 0xf1, 0x10, 0x10, 0x10, + 0xf1, 0x10, 0x10, 0x10, + 0xf1, 0x10, 0x10, 0x10, + 0xf1, 0x10, 0x10, 0x10, + 0xf1, 0x10, 0x10, 0x10, + 0xf1, 0x10, 0x10, 0x10, + 0xf1, 0x10, 0x10, 0x10, + 0xf1, 0x10, 0x10, 0x10, + 0xf1, 0x10, 0x10, 0x10, + 0xf1, 0x10, 0x10, 0x10, + 0xf1, 0x10, 0x10, 0x10, + 0xf1, 0x10, 0x10, 0x10, + 0xf1, 0x10, 0x10, 0x10, + 0xf1, 0x10, 0x10, 0x10, + 0xf1, 0x10, 0x10, 0x10, + 0xf1, 0x10, 0x10, 0x10, + 0xf1, 0x10, 0x10, 0x10 + }; + constexpr auto result = chars::EncodingType::UTF16_LE; + + constexpr auto source_length = std::ranges::size(source_u8); + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + + const auto encoding = Impl::encoding_of(source); + expect(encoding == value(result)); + }; + + "UTF32"_test = [] + { + constexpr std::uint8_t source_u8[] + { + 0x00, 0xd8, 0x01, 0x00, + 0x00, 0xd8, 0x01, 0x00, + 0x00, 0xd8, 0x01, 0x00, + 0x00, 0xd8, 0x01, 0x00, + 0x00, 0xd8, 0x01, 0x00, + 0x00, 0xd8, 0x01, 0x00, + 0x00, 0xd8, 0x01, 0x00, + 0x00, 0xd8, 0x01, 0x00, + 0x00, 0xd8, 0x01, 0x00, + 0x00, 0xd8, 0x01, 0x00, + 0x00, 0xd8, 0x01, 0x00, + 0x00, 0xd8, 0x01, 0x00, + 0x00, 0xd8, 0x01, 0x00, + 0x00, 0xd8, 0x01, 0x00, + 0x00, 0xd8, 0x01, 0x00, + 0x00, 0xd8, 0x01, 0x00, + 0x00, 0xd8, 0x01, 0x00, + }; + constexpr auto result = chars::EncodingType::UTF32_LE; + + constexpr auto source_length = std::ranges::size(source_u8); + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + + const auto encoding = Impl::encoding_of(source); + expect(encoding == value(result)); + }; + + "ALL"_test = [] + { + constexpr std::uint8_t source_u8[] + { + 0x7e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00, + 0x7e, 0x00, 0x00, 0x00 + }; + constexpr auto result = static_cast( + std::to_underlying(chars::EncodingType::UTF8) | + std::to_underlying(chars::EncodingType::UTF16_LE) | + std::to_underlying(chars::EncodingType::UTF32_LE) + ); + + constexpr auto source_length = std::ranges::size(source_u8); + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + + const auto encoding = Impl::encoding_of(source); + expect(encoding == value(result)); + }; + + "ERROR"_test = [] + { + constexpr std::uint8_t source_u8[] + { + 0x00, 0xd8, 0x00, 0xd8, + 0x00, 0xd8, 0x00, 0xd8, + 0x00, 0xd8, 0x00, 0xd8, + 0x00, 0xd8, 0x00, 0xd8, + 0x00, 0xd8, 0x00, 0xd8, + 0x00, 0xd8, 0x00, 0xd8, + 0x00, 0xd8, 0x00, 0xd8, + 0x00, 0xd8, 0x00, 0xd8, + 0x00, 0xd8, 0x00, 0xd8, + 0x00, 0xd8, 0x00, 0xd8, + 0x00, 0xd8, 0x00, 0xd8, + 0x00, 0xd8, 0x00, 0xd8, + 0x00, 0xd8, 0x00, 0xd8, + 0x00, 0xd8, 0x00, 0xd8, + 0x00, 0xd8, 0x00, 0xd8, + 0x00, 0xd8, 0x00, 0xd8, + 0x00, 0xd8, 0x00, 0xd8 + }; + constexpr auto result = chars::EncodingType::UNKNOWN; + + constexpr auto source_length = std::ranges::size(source_u8); + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + + const auto encoding = Impl::encoding_of(source); + expect(encoding == value(result)); + }; +} + +// ============================== +// LATIN + +template +constexpr auto make_test_latin_error() noexcept -> void +{ + using namespace gal::prometheus::unit_test; + + "too_large"_test = [] + { + constexpr std::uint8_t source_u8[] + { + 0x61, // 0 + 0x61, // 1 + 0x61, // 2 + 0x61, // 3 + 0x61, // 4 + 0x61, // 5 + 0x61, // 6 + 0x61, // 7 + 0x61, // 8 + 0x61, // 9 + 0x80, // 10 <-- ERROR + 0x61, // 11 + 0x61, // 12 + 0x61, // 13 + 0x61, // 14 + 0x61, // 15 + 0x61, // 16 + 0x61, // 17 + 0x61, // 18 + 0x61, // 19 + 0x61, // 20 + 0x61, // 21 + 0x61, // 22 + 0x61, // 23 + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x00, + }; + + constexpr auto source_length = std::ranges::size(source_u8); + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + + make_test(source, ErrorCode::TOO_LARGE, 10); + }; +} + +// ============================== +// UTF8/UTF8_CHAR + +template +constexpr auto make_test_utf8_error() noexcept -> void +{ + using namespace gal::prometheus::unit_test; + + "overlong"_test = [] + { + constexpr std::uint8_t source_u8[] + { + 0x61, // 0 + 0x61, // 1 + 0x61, // 2 + 0x61, // 3 + 0x61, // 4 + 0x61, // 5 + 0x61, // 6 + 0x61, // 7 + 0x61, // 8 + 0x61, // 9 + 0xC0, 0xAF, // 10+11 <-- ERROR '/'(U+002F) + 0x61, // 12 + 0x61, // 13 + 0x61, // 14 + 0x61, // 15 + 0x61, // 16 + 0x61, // 17 + 0x61, // 18 + 0x61, // 19 + 0x61, // 20 + 0x61, // 21 + 0x61, // 22 + 0x61, // 23 + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x00, + }; + + constexpr auto source_length = std::ranges::size(source_u8); + + // UTF8_CHAR + { + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + make_test(source, ErrorCode::OVERLONG, 10); + } + // UTF8 + { + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + make_test(source, ErrorCode::OVERLONG, 10); + } + }; + + "surrogate"_test = [] + { + constexpr std::uint8_t source_u8[] + { + 0x61, // 0 + 0x61, // 1 + 0x61, // 2 + 0x61, // 3 + 0x61, // 4 + 0x61, // 5 + 0x61, // 6 + 0x61, // 7 + 0x61, // 8 + 0x61, // 9 + 0xED, 0xA0, 0x80, // 10+11+12 <-- ERROR (U+D800) + 0x61, // 13 + 0x61, // 14 + 0x61, // 15 + 0x61, // 16 + 0x61, // 17 + 0x61, // 18 + 0x61, // 19 + 0x61, // 20 + 0x61, // 21 + 0x61, // 22 + 0x61, // 23 + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x00, + }; + + constexpr auto source_length = std::ranges::size(source_u8); + + // UTF8_CHAR + { + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + make_test(source, ErrorCode::SURROGATE, 10); + } + // UTF8 + { + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + make_test(source, ErrorCode::SURROGATE, 10); + } + }; + + "bad continuation byte"_test = [] + { + constexpr std::uint8_t source_u8[] + { + 0x61, // 0 + 0x61, // 1 + 0x61, // 2 + 0x61, // 3 + 0x61, // 4 + 0x61, // 5 + 0x61, // 6 + 0x61, // 7 + 0x61, // 8 + 0x61, // 9 + 0xC2, // 10 <-- ERROR (missing/invalid) continuation byte + 0x61, // 11 + 0x61, // 12 + 0x61, // 13 + 0x61, // 14 + 0x61, // 15 + 0x61, // 16 + 0x61, // 17 + 0x61, // 18 + 0x61, // 19 + 0x61, // 20 + 0x61, // 21 + 0x61, // 22 + 0x61, // 23 + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x00, + }; + + constexpr auto source_length = std::ranges::size(source_u8); + + // UTF8_CHAR + { + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + make_test(source, ErrorCode::TOO_SHORT, 10); + } + // UTF8 + { + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + make_test(source, ErrorCode::TOO_SHORT, 10); + } + }; + + "too many continuation bytes"_test = [] + { + constexpr std::uint8_t source_u8[] + { + 0x61, // 0 + 0x61, // 1 + 0x61, // 2 + 0x61, // 3 + 0x61, // 4 + 0x61, // 5 + 0x61, // 6 + 0x61, // 7 + 0x61, // 8 + 0x61, // 9 + 0x80, 0x80, // 10+11 <-- ERROR too many continuation bytes/missing leading byte + 0x61, // 12 + 0x61, // 13 + 0x61, // 14 + 0x61, // 15 + 0x61, // 16 + 0x61, // 17 + 0x61, // 18 + 0x61, // 19 + 0x61, // 20 + 0x61, // 21 + 0x61, // 22 + 0x61, // 23 + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x00, + }; + + constexpr auto source_length = std::ranges::size(source_u8); + + // UTF8_CHAR + { + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + make_test(source, ErrorCode::TOO_LONG, 10); + } + // UTF8 + { + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + make_test(source, ErrorCode::TOO_LONG, 10); + } + }; + + "header bits"_test = [] + { + constexpr std::uint8_t source_u8[] + { + 0x61, // 0 + 0x61, // 1 + 0x61, // 2 + 0x61, // 3 + 0x61, // 4 + 0x61, // 5 + 0x61, // 6 + 0x61, // 7 + 0x61, // 8 + 0x61, // 9 + 0xF8, 0x88, 0x80, 0x80, 0x80, // 10+11+12+13+14 <-- ERROR invalid leading byte + 0x61, // 15 + 0x61, // 16 + 0x61, // 17 + 0x61, // 18 + 0x61, // 19 + 0x61, // 20 + 0x61, // 21 + 0x61, // 22 + 0x61, // 23 + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x00, + }; + + constexpr auto source_length = std::ranges::size(source_u8); + + // UTF8_CHAR + { + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + make_test(source, ErrorCode::HEADER_BITS, 10); + } + // UTF8 + { + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + make_test(source, ErrorCode::HEADER_BITS, 10); + } + }; + + "too large"_test = [] + { + constexpr std::uint8_t source_u8[] + { + 0x61, // 0 + 0x61, // 1 + 0x61, // 2 + 0x61, // 3 + 0x61, // 4 + 0x61, // 5 + 0x61, // 6 + 0x61, // 7 + 0x61, // 8 + 0x61, // 9 + 0xF4, 0x90, 0x80, 0x80, // 10+11+12+13 <-- ERROR (U+110000) greater than U+10FFFF + 0x61, // 14 + 0x61, // 15 + 0x61, // 16 + 0x61, // 17 + 0x61, // 18 + 0x61, // 19 + 0x61, // 20 + 0x61, // 21 + 0x61, // 22 + 0x61, // 23 + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x00, + }; + + constexpr auto source_length = std::ranges::size(source_u8); + + // UTF8_CHAR + { + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + make_test(source, ErrorCode::TOO_LARGE, 10); + } + // UTF8 + { + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + make_test(source, ErrorCode::TOO_LARGE, 10); + } + }; + + "truncated"_test = [] + { + constexpr std::uint8_t source_u8[] + { + 0x61, // 0 + 0x61, // 1 + 0x61, // 2 + 0x61, // 3 + 0x61, // 4 + 0x61, // 5 + 0x61, // 6 + 0x61, // 7 + 0x61, // 8 + 0x61, // 9 + 0xF0, 0x9F, 0x98, // 10+11+13 <-- ERROR emoji(😀), missing last byte(0x80) + 0x61, // 13 + 0x61, // 14 + 0x61, // 15 + 0x61, // 16 + 0x61, // 17 + 0x61, // 18 + 0x61, // 19 + 0x61, // 20 + 0x61, // 21 + 0x61, // 22 + 0x61, // 23 + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x00, + }; + + constexpr auto source_length = std::ranges::size(source_u8); + + // UTF8_CHAR + { + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + make_test(source, ErrorCode::TOO_SHORT, 10); + } + // UTF8 + { + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + make_test(source, ErrorCode::TOO_SHORT, 10); + } + }; +} + +// ============================== +// UTF16_LE/UTF16_BE + +template +constexpr auto make_test_utf16_error() noexcept -> void +{ + using namespace gal::prometheus::unit_test; + + // high + ? + "missing/invalid low surrogate"_test = [] + { + alignas(alignof(char16_t)) constexpr std::uint8_t source_u8[] + { + 0x61, 0x00, // 0 + 0x61, 0x00, // 1 + 0x61, 0x00, // 2 + 0x61, 0x00, // 3 + 0x61, 0x00, // 4 + 0x61, 0x00, // 5 + 0x61, 0x00, // 6 + 0x61, 0x00, // 7 + 0x61, 0x00, // 8 + 0x61, 0x00, // 9 + 0x00, 0xD8, // 10 <-- ERROR high surrogate only, missing/invalid low surrogate + 0x61, 0x00, // 11 + 0x61, 0x00, // 12 + 0x61, 0x00, // 13 + 0x61, 0x00, // 14 + 0x61, 0x00, // 15 + 0x61, 0x00, // 16 + 0x61, 0x00, // 17 + 0x61, 0x00, // 18 + 0x61, 0x00, // 19 + 0x61, 0x00, // 20 + 0x61, 0x00, // 21 + 0x61, 0x00, // 22 + 0x61, 0x00, // 23 + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x00, 0x00, + }; + + constexpr auto source_length = std::ranges::size(source_u8) / 2; + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + + make_test(source, ErrorCode::SURROGATE, 10); + }; + + // ? + low + "unexpected low surrogate"_test = [] + { + alignas(alignof(char16_t)) constexpr std::uint8_t source_u8[] + { + 0x61, 0x00, // 0 + 0x61, 0x00, // 1 + 0x61, 0x00, // 2 + 0x61, 0x00, // 3 + 0x61, 0x00, // 4 + 0x61, 0x00, // 5 + 0x61, 0x00, // 6 + 0x61, 0x00, // 7 + 0x61, 0x00, // 8 + 0x61, 0x00, // 9 + 0x00, 0xDC, // 10 <-- ERROR unexpected low surrogate + 0x61, 0x00, // 11 + 0x61, 0x00, // 12 + 0x61, 0x00, // 13 + 0x61, 0x00, // 14 + 0x61, 0x00, // 15 + 0x61, 0x00, // 16 + 0x61, 0x00, // 17 + 0x61, 0x00, // 18 + 0x61, 0x00, // 19 + 0x61, 0x00, // 20 + 0x61, 0x00, // 21 + 0x61, 0x00, // 22 + 0x61, 0x00, // 23 + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x00, 0x00, + }; + + constexpr auto source_length = std::ranges::size(source_u8) / 2; + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + + make_test(source, ErrorCode::SURROGATE, 10); + }; + + // high + high + "duo high surrogate"_test = [] + { + alignas(alignof(char16_t)) constexpr std::uint8_t source_u8[] + { + 0x61, 0x00, // 0 + 0x61, 0x00, // 1 + 0x61, 0x00, // 2 + 0x61, 0x00, // 3 + 0x61, 0x00, // 4 + 0x61, 0x00, // 5 + 0x61, 0x00, // 6 + 0x61, 0x00, // 7 + 0x61, 0x00, // 8 + 0x61, 0x00, // 9 + 0x00, 0xD8, 0x01, 0xD8, // 10+11 <-- ERROR duo high surrogate + 0x61, 0x00, // 12 + 0x61, 0x00, // 13 + 0x61, 0x00, // 14 + 0x61, 0x00, // 15 + 0x61, 0x00, // 16 + 0x61, 0x00, // 17 + 0x61, 0x00, // 18 + 0x61, 0x00, // 19 + 0x61, 0x00, // 20 + 0x61, 0x00, // 21 + 0x61, 0x00, // 22 + 0x61, 0x00, // 23 + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x00, 0x00, + }; + + constexpr auto source_length = std::ranges::size(source_u8) / 2; + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + + make_test(source, ErrorCode::SURROGATE, 10); + }; + + // low + low + "duo low surrogate"_test = [] + { + alignas(alignof(char16_t)) constexpr std::uint8_t source_u8[] + { + 0x61, 0x00, // 0 + 0x61, 0x00, // 1 + 0x61, 0x00, // 2 + 0x61, 0x00, // 3 + 0x61, 0x00, // 4 + 0x61, 0x00, // 5 + 0x61, 0x00, // 6 + 0x61, 0x00, // 7 + 0x61, 0x00, // 8 + 0x61, 0x00, // 9 + 0x00, 0xDC, 0x01, 0xDC, // 10+11 <-- ERROR duo low surrogate + 0x61, 0x00, // 12 + 0x61, 0x00, // 13 + 0x61, 0x00, // 14 + 0x61, 0x00, // 15 + 0x61, 0x00, // 16 + 0x61, 0x00, // 17 + 0x61, 0x00, // 18 + 0x61, 0x00, // 19 + 0x61, 0x00, // 20 + 0x61, 0x00, // 21 + 0x61, 0x00, // 22 + 0x61, 0x00, // 23 + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, 0x61, 0x00, + 0x00, 0x00, + }; + + constexpr auto source_length = std::ranges::size(source_u8) / 2; + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + + make_test(source, ErrorCode::SURROGATE, 10); + }; +} + +// ============================== +// UTF32 + +template +constexpr auto make_test_utf32_error() noexcept -> void +{ + using namespace gal::prometheus::unit_test; + + "surrogate"_test = [] + { + alignas(alignof(char32_t)) constexpr std::uint8_t source_u8[] + { + 0x61, 0x00, 0x00, 0x00, // 0 + 0x61, 0x00, 0x00, 0x00, // 1 + 0x61, 0x00, 0x00, 0x00, // 2 + 0x61, 0x00, 0x00, 0x00, // 3 + 0x61, 0x00, 0x00, 0x00, // 4 + 0x61, 0x00, 0x00, 0x00, // 5 + 0x61, 0x00, 0x00, 0x00, // 6 + 0x61, 0x00, 0x00, 0x00, // 7 + 0x61, 0x00, 0x00, 0x00, // 8 + 0x61, 0x00, 0x00, 0x00, // 9 + 0x00, 0xD8, 0x00, 0x00, // 10 <-- ERROR surrogate + 0x61, 0x00, 0x00, 0x00, // 11 + 0x61, 0x00, 0x00, 0x00, // 12 + 0x61, 0x00, 0x00, 0x00, // 13 + 0x61, 0x00, 0x00, 0x00, // 14 + 0x61, 0x00, 0x00, 0x00, // 15 + 0x61, 0x00, 0x00, 0x00, // 16 + 0x61, 0x00, 0x00, 0x00, // 17 + 0x61, 0x00, 0x00, 0x00, // 18 + 0x61, 0x00, 0x00, 0x00, // 19 + 0x61, 0x00, 0x00, 0x00, // 20 + 0x61, 0x00, 0x00, 0x00, // 21 + 0x61, 0x00, 0x00, 0x00, // 22 + 0x61, 0x00, 0x00, 0x00, // 23 + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + constexpr auto source_length = std::ranges::size(source_u8) / 4; + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + + make_test(source, ErrorCode::SURROGATE, 10); + }; + + "too large"_test = [] + { + alignas(alignof(char32_t)) constexpr std::uint8_t source_u8[] + { + 0x61, 0x00, 0x00, 0x00, // 0 + 0x61, 0x00, 0x00, 0x00, // 1 + 0x61, 0x00, 0x00, 0x00, // 2 + 0x61, 0x00, 0x00, 0x00, // 3 + 0x61, 0x00, 0x00, 0x00, // 4 + 0x61, 0x00, 0x00, 0x00, // 5 + 0x61, 0x00, 0x00, 0x00, // 6 + 0x61, 0x00, 0x00, 0x00, // 7 + 0x61, 0x00, 0x00, 0x00, // 8 + 0x61, 0x00, 0x00, 0x00, // 9 + 0xF4, 0x90, 0x80, 0x80, // 10+11+12+13 <-- ERROR (U+110000) greater than U+10FFFF + 0x61, 0x00, 0x00, 0x00, // 11 + 0x61, 0x00, 0x00, 0x00, // 12 + 0x61, 0x00, 0x00, 0x00, // 13 + 0x61, 0x00, 0x00, 0x00, // 14 + 0x61, 0x00, 0x00, 0x00, // 15 + 0x61, 0x00, 0x00, 0x00, // 16 + 0x61, 0x00, 0x00, 0x00, // 17 + 0x61, 0x00, 0x00, 0x00, // 18 + 0x61, 0x00, 0x00, 0x00, // 19 + 0x61, 0x00, 0x00, 0x00, // 20 + 0x61, 0x00, 0x00, 0x00, // 21 + 0x61, 0x00, 0x00, 0x00, // 22 + 0x61, 0x00, 0x00, 0x00, // 23 + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + constexpr auto source_length = std::ranges::size(source_u8) / 4; + const auto source = std::span{reinterpret_cast(source_u8), source_length}; + + make_test(source, ErrorCode::TOO_LARGE, 10); + }; +} diff --git a/unit_test/src/chars/icelake.cpp b/unit_test/src/chars/icelake.cpp new file mode 100644 index 00000000..7058632d --- /dev/null +++ b/unit_test/src/chars/icelake.cpp @@ -0,0 +1,338 @@ +#include +#include + +#include "gen.hpp" + +using namespace gal::prometheus; + +#if GAL_PROMETHEUS_CPU_FEATURES_ICELAKE_SUPPORTED + +namespace +{ + [[maybe_unused]] GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.detect_encoding.icelake"> detect_encoding = [] + { + using namespace chars; + + make_test_detect_encoding(); + }; + + [[maybe_unused]] GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.latin.icelake"> latin = [] + { + using namespace unit_test; + using namespace chars; + + "error"_test = [] + { + make_test_latin_error(); + }; + + constexpr std::size_t trials = 1000; + + "to_utf8_char"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_latin_string()); + } + }; + + "to_utf8"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_latin_string()); + } + }; + + "to_utf16_le"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_latin_string()); + } + }; + + "to_utf16_be"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_latin_string()); + } + }; + + "to_utf32"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_latin_string()); + } + }; + }; + + [[maybe_unused]] GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.utf8.char.icelake"> utf8_char = [] + { + using namespace unit_test; + using namespace chars; + + "error"_test = [] + { + make_test_utf8_error(); + }; + + constexpr std::size_t trials = 1000; + + "to_latin"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_char_string_ascii_only()); + } + }; + + "to_utf8"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_char_string()); + } + }; + + "to_utf16_le"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_char_string()); + } + }; + + "to_utf16_be"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_char_string()); + } + }; + + "to_utf32"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_char_string()); + } + }; + }; + + [[maybe_unused]] GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.utf8.icelake"> utf8 = [] + { + using namespace unit_test; + using namespace chars; + + "error"_test = [] + { + make_test_utf8_error(); + }; + + constexpr std::size_t trials = 1000; + + "to_latin"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_string_ascii_only()); + } + }; + + "to_utf8_char"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_string()); + } + }; + + "to_utf16_le"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_string()); + } + }; + + "to_utf16_be"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_string()); + } + }; + + "to_utf32"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_string()); + } + }; + }; + + [[maybe_unused]] GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.utf16.le.icelake"> utf16_le = [] + { + using namespace unit_test; + using namespace chars; + + "error"_test = [] + { + make_test_utf16_error(); + }; + + constexpr std::size_t trials = 1000; + + "to_latin"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_le_string_ascii_only()); + } + }; + + "to_utf8_char"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_le_string()); + } + }; + + "to_utf8"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_le_string()); + } + }; + + "to_utf16_be"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_le_string()); + } + }; + + "to_utf32"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_le_string()); + } + }; + }; + + [[maybe_unused]] GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.utf16.be.icelake"> utf16_be = [] + { + using namespace unit_test; + using namespace chars; + + "error"_test = [] + { + make_test_utf16_error(); + }; + + constexpr std::size_t trials = 1000; + + "to_latin"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_be_string_ascii_only()); + } + }; + + "to_utf8_char"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_be_string()); + } + }; + + "to_utf8"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_be_string()); + } + }; + + "to_utf16_le"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_be_string()); + } + }; + + "to_utf32"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_be_string()); + } + }; + }; + + [[maybe_unused]] GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.utf32.icelake"> utf32 = [] + { + using namespace unit_test; + using namespace chars; + + "error"_test = [] + { + make_test_utf32_error(); + }; + + constexpr std::size_t trials = 1000; + + "to_latin"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf32_string_ascii_only()); + } + }; + + "to_utf8_char"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf32_string()); + } + }; + + "to_utf8"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf32_string()); + } + }; + + "to_utf16_le"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf32_string()); + } + }; + + "to_utf16_be"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf32_string()); + } + }; + }; +} + +#endif diff --git a/unit_test/src/chars/scalar.cpp b/unit_test/src/chars/scalar.cpp new file mode 100644 index 00000000..7a719158 --- /dev/null +++ b/unit_test/src/chars/scalar.cpp @@ -0,0 +1,334 @@ +#include +#include + +#include "gen.hpp" + +using namespace gal::prometheus; + +namespace +{ + [[maybe_unused]] GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.detect_encoding.scalar"> detect_encoding = [] + { + using namespace chars; + + make_test_detect_encoding(); + }; + + [[maybe_unused]] GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.latin.scalar"> latin = [] + { + using namespace unit_test; + using namespace chars; + + "error"_test = [] + { + make_test_latin_error(); + }; + + constexpr std::size_t trials = 1000; + + "to_utf8_char"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_latin_string()); + } + }; + + "to_utf8"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_latin_string()); + } + }; + + "to_utf16_le"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_latin_string()); + } + }; + + "to_utf16_be"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_latin_string()); + } + }; + + "to_utf32"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_latin_string()); + } + }; + }; + + [[maybe_unused]] GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.utf8.char.scalar"> utf8_char = [] + { + using namespace unit_test; + using namespace chars; + + "error"_test = [] + { + make_test_utf8_error(); + }; + + constexpr std::size_t trials = 1000; + + "to_latin"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_char_string_ascii_only()); + } + }; + + "to_utf8"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_char_string()); + } + }; + + "to_utf16_le"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_char_string()); + } + }; + + "to_utf16_be"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_char_string()); + } + }; + + "to_utf32"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_char_string()); + } + }; + }; + + [[maybe_unused]] GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.utf8.scalar"> utf8 = [] + { + using namespace unit_test; + using namespace chars; + + "error"_test = [] + { + make_test_utf8_error(); + }; + + constexpr std::size_t trials = 1000; + + "to_latin"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_string_ascii_only()); + } + }; + + "to_utf8_char"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_string()); + } + }; + + "to_utf16_le"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_string()); + } + }; + + "to_utf16_be"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_string()); + } + }; + + "to_utf32"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf8_string()); + } + }; + }; + + [[maybe_unused]] GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.utf16.le.scalar"> utf16_le = [] + { + using namespace unit_test; + using namespace chars; + + "error"_test = [] + { + make_test_utf16_error(); + }; + + constexpr std::size_t trials = 1000; + + "to_latin"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_le_string_ascii_only()); + } + }; + + "to_utf8_char"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_le_string()); + } + }; + + "to_utf8"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_le_string()); + } + }; + + "to_utf16_be"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_le_string()); + } + }; + + "to_utf32"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_le_string()); + } + }; + }; + + [[maybe_unused]] GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.utf16.be.scalar"> utf16_be = [] + { + using namespace unit_test; + using namespace chars; + + "error"_test = [] + { + make_test_utf16_error(); + }; + + constexpr std::size_t trials = 1000; + + "to_latin"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_be_string_ascii_only()); + } + }; + + "to_utf8_char"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_be_string()); + } + }; + + "to_utf8"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_be_string()); + } + }; + + "to_utf16_le"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_be_string()); + } + }; + + "to_utf32"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf16_be_string()); + } + }; + }; + + [[maybe_unused]] GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.utf32.scalar"> utf32 = [] + { + using namespace unit_test; + using namespace chars; + + "error"_test = [] + { + make_test_utf32_error(); + }; + + constexpr std::size_t trials = 1000; + + "to_latin"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf32_string_ascii_only()); + } + }; + + "to_utf8_char"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf32_string()); + } + }; + + "to_utf8"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf32_string()); + } + }; + + "to_utf16_le"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf32_string()); + } + }; + + "to_utf16_be"_test = [] + { + for (std::size_t i = 0; i < trials; ++i) + { + make_test(make_random_utf32_string()); + } + }; + }; +} diff --git a/unit_test/src/chars/utf16.cpp b/unit_test/src/chars/utf16.cpp deleted file mode 100644 index 08a340a8..00000000 --- a/unit_test/src/chars/utf16.cpp +++ /dev/null @@ -1,288 +0,0 @@ -#include - -#if GAL_PROMETHEUS_USE_MODULE -import std; -import gal.prometheus; -#else -#include - -#include -#include -#include -#endif - -namespace -{ - using namespace gal::prometheus; - - [[nodiscard]] auto make_source(auto generator, const std::size_t size) -> std::basic_string - { - std::basic_string source{}; - for (std::size_t i = 0; i < size;) - { - i += generator(source); - } - - return source; - } - - GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.utf16_le"> chars_utf16_le = [] - { - using namespace unit_test; - using namespace literals; - - const auto old_level = std::exchange(config().output_level, OutputLevel::NONE); - - numeric::Random random; - const auto generator = [&random](auto& source) mutable -> std::size_t - { - unsigned v; - do - { - v = random.get(0, 0x0010'ffef); - } while ( - // skip surrogate pairs - (v >= 0xd800 and v <= 0xdfff) // or - // // skip non-characters - // (v >= 0xfdd0 and v <= 0xfdef) or (v & 0xfffe) == 0xfffe or - // // skip control characters - // (v <= 0x001f) or (v >= 0x007f and v <= 0x009f) or - // // skip special characters - // (v >= 0xfff0 and v <= 0xfff8) or - // // skip tag characters and variation selector supplement - // (v >= 0x000e'0000 and v <= 0x000e'01ef) or - // // skip private use areas - // (v >= 0x000f'0000) - ); - - if (v < 0xffff) - { - source.push_back(static_cast(v)); - return 1; - } - else - { - v -= 0x0001'0000; - source.push_back(static_cast(0xd800 | ((v >> 10) & 0x03ff))); - source.push_back(static_cast(0xdc00 | (v & 0x03ff))); - return 2; - } - }; - const auto generator_char_only = [&random](auto& source) mutable -> std::size_t - { - source.push_back(static_cast(random.get() & std::numeric_limits::max())); - return 1; - }; - - constexpr std::size_t trials = 1000; - for (std::size_t i = 0; i < trials; ++i) - { - "to_ascii"_test = [source = make_source(generator_char_only, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf16 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid ascii string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid ascii string"_b) << fatal; - }; - - "to_utf8_char"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf16 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf8_char string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf8_char string"_b) << fatal; - }; - - "to_utf8"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf16 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf8 string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf8 string"_b) << fatal; - }; - - "to_utf16_le"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf16 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect((dest == ref(source)) == "valid utf16 string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect((dest_a == ref(source)) == "valid utf16 string"_b) << fatal; - }; - - "to_utf16_be"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf16 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf16_be string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf16_be string"_b) << fatal; - - const auto flipped = chars::flip_endian(dest_a); - expect((flipped == ref(source)) == "valid utf16 string"_b) << fatal; - }; - - "to_utf32"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf16 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf32 string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf32 string"_b) << fatal; - }; - } - - config().output_level = old_level; - }; - - GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.utf16_be"> chars_utf16_be = [] - { - using namespace unit_test; - using namespace literals; - - const auto old_level = std::exchange(config().output_level, OutputLevel::NONE); - - numeric::Random random; - const auto generator = [&random](auto& source) mutable -> std::size_t - { - unsigned v; - do - { - v = random.get(0, 0x0010'ffef); - } while ( - // skip surrogate pairs - (v >= 0xd800 and v <= 0xdfff) // or - // // skip non-characters - // (v >= 0xfdd0 and v <= 0xfdef) or (v & 0xfffe) == 0xfffe or - // // skip control characters - // (v <= 0x001f) or (v >= 0x007f and v <= 0x009f) or - // // skip special characters - // (v >= 0xfff0 and v <= 0xfff8) or - // // skip tag characters and variation selector supplement - // (v >= 0x000e'0000 and v <= 0x000e'01ef) or - // // skip private use areas - // (v >= 0x000f'0000) - ); - - if (v < 0xffff) - { - source.push_back(static_cast(std::byteswap(v))); - return 1; - } - else - { - v -= 0x0001'0000; - source.push_back(static_cast(std::byteswap(0xd800 | ((v >> 10) & 0x03ff)))); - source.push_back(static_cast(std::byteswap(0xdc00 | (v & 0x03ff)))); - return 2; - } - }; - const auto generator_char_only = [&random](auto& source) mutable -> std::size_t - { - source.push_back(static_cast(random.get() & std::numeric_limits::max())); - return 1; - }; - - constexpr std::size_t trials = 1000; - for (std::size_t i = 0; i < trials; ++i) - { - "to_ascii"_test = [source = make_source(generator_char_only, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf16 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid ascii string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid ascii string"_b) << fatal; - }; - - "to_utf8_char"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf16 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf8_char string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf8_char string"_b) << fatal; - }; - - "to_utf8"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf16 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf8 string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf8 string"_b) << fatal; - }; - - "to_utf16_le"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf16 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect((dest == ref(source)) == "valid utf16 string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect((dest_a == ref(source)) == "valid utf16 string"_b) << fatal; - - const auto flipped = chars::flip_endian(dest_a); - expect((flipped == ref(source)) == "valid utf16 string"_b) << fatal; - }; - - "to_utf16_be"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf16 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf16_be string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf16_be string"_b) << fatal; - }; - - "to_utf32"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf16 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf32 string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf32 string"_b) << fatal; - }; - } - - config().output_level = old_level; - }; -} diff --git a/unit_test/src/chars/utf32.cpp b/unit_test/src/chars/utf32.cpp deleted file mode 100644 index 8e6d761d..00000000 --- a/unit_test/src/chars/utf32.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include - -#if GAL_PROMETHEUS_USE_MODULE -import std; -import gal.prometheus; -#else -#include - -#include -#include -#include -#endif - -namespace -{ - using namespace gal::prometheus; - - [[nodiscard]] auto make_source(auto generator, const std::size_t size) -> std::basic_string - { - std::basic_string source{}; - for (std::size_t i = 0; i < size;) - { - i += generator(source); - } - - return source; - } - - GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.utf32"> _ = [] - { - using namespace unit_test; - using namespace literals; - - const auto old_level = std::exchange(config().output_level, OutputLevel::NONE); - - numeric::Random random; - const auto generator = [&random](auto& source) mutable -> std::size_t - { - unsigned v; - do - { - v = random.get(0, 0x0010'ffef); - } while ( - // skip surrogate pairs - (v >= 0xd800 and v <= 0xdfff) // or - // // skip non-characters - // (v >= 0xfdd0 and v <= 0xfdef) or (v & 0xfffe) == 0xfffe or - // // skip control characters - // (v <= 0x001f) or (v >= 0x007f and v <= 0x009f) or - // // skip special characters - // (v >= 0xfff0 and v <= 0xfff8) or - // // skip tag characters and variation selector supplement - // (v >= 0x000e'0000 and v <= 0x000e'01ef) or - // // skip private use areas - // (v >= 0x000f'0000) - ); - - source.push_back(static_cast(v)); - return 1; - }; - const auto generator_char_only = [&random](auto& source) mutable -> std::size_t - { - source.push_back(static_cast(random.get() & std::numeric_limits::max())); - return 1; - }; - - constexpr std::size_t trials = 1000; - for (std::size_t i = 0; i < trials; ++i) - { - "to_ascii"_test = [source = make_source(generator_char_only, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf32 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid ascii string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid ascii string"_b) << fatal; - }; - - "to_utf8_char"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf32 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf8 string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf8 string"_b) << fatal; - }; - - "to_utf8"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf32 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf8 string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf8 string"_b) << fatal; - }; - - "to_utf16_le"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf32 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf16_le string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf16_le string"_b) << fatal; - }; - - "to_utf16_be"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf32 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf16_be string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf16_be string"_b) << fatal; - }; - - "to_utf32"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf32 string"_b) << fatal; - expect(chars::length(source) == value(source.size())) << fatal; - - const auto dest = chars::convert(source); - expect((dest == ref(source)) == "valid utf32 string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect((dest_a == ref(source)) == "valid utf32 string"_b) << fatal; - }; - } - - config().output_level = old_level; - }; -} diff --git a/unit_test/src/chars/utf8.cpp b/unit_test/src/chars/utf8.cpp deleted file mode 100644 index 6d1eff6d..00000000 --- a/unit_test/src/chars/utf8.cpp +++ /dev/null @@ -1,322 +0,0 @@ -#include - -#if GAL_PROMETHEUS_USE_MODULE -import std; -import gal.prometheus; -#else -#include - -#include -#include -#include -#endif - -namespace -{ - using namespace gal::prometheus; - - template - [[nodiscard]] auto make_source(auto generator, const std::size_t size) -> std::basic_string - { - std::basic_string source{}; - for (std::size_t i = 0; i < size;) - { - i += generator(source); - } - - return source; - } - - GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.utf8_char"> chars_utf8_char = [] - { - using namespace unit_test; - using namespace literals; - - const auto old_level = std::exchange(config().output_level, OutputLevel::NONE); - - numeric::Random random; - const auto generator = [&random](auto& source) mutable -> std::size_t - { - unsigned v; - do - { - v = random.get(0, 0x0010'ffef); - } while ( - // skip surrogate pairs - (v >= 0xd800 and v <= 0xdfff) // or - // // skip non-characters - // (v >= 0xfdd0 and v <= 0xfdef) or (v & 0xfffe) == 0xfffe or - // // skip control characters - // (v <= 0x001f) or (v >= 0x007f and v <= 0x009f) or - // // skip special characters - // (v >= 0xfff0 and v <= 0xfff8) or - // // skip tag characters and variation selector supplement - // (v >= 0x000e'0000 and v <= 0x000e'01ef) or - // // skip private use areas - // (v >= 0x000f'0000) - ); - - if (v < 0x0080) - { - source.push_back(static_cast(v)); - return 1; - } - else if (v < 0x0800) - { - source.push_back(static_cast(0xc0 | (v >> 6))); - source.push_back(static_cast(0x80 | (v & 0x3f))); - return 2; - } - else if (v < 0x0001'0000) - { - source.push_back(static_cast(0xe0 | (v >> 12))); - source.push_back(static_cast(0x80 | ((v >> 6) & 0x3f))); - source.push_back(static_cast(0x80 | (v & 0x3f))); - return 3; - } - else - { - source.push_back(static_cast(0xf0 | (v >> 18))); - source.push_back(static_cast(0x80 | ((v >> 12) & 0x3f))); - source.push_back(static_cast(0x80 | ((v >> 6) & 0x3f))); - source.push_back(static_cast(0x80 | (v & 0x3f))); - return 4; - } - }; - const auto generator_char_only = [&random](auto& source) mutable -> std::size_t - { - source.push_back(static_cast(random.get() & std::numeric_limits::max())); - return 1; - }; - - constexpr std::size_t trials = 1000; - for (std::size_t i = 0; i < trials; ++i) - { - "to_ascii"_test = [source = make_source(generator_char_only, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf8_char string"_b) << fatal; - expect(chars::length(source) == value(source.size())) << fatal; - - const auto dest = chars::convert(source); - expect((dest == ref(source)) == "valid ascii string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect((dest_a == ref(source)) == "valid ascii string"_b) << fatal; - }; - - "to_utf8_char"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf8_char string"_b) << fatal; - expect(chars::length(source) == value(source.size())) << fatal; - - const auto dest = chars::convert(source); - expect((dest == ref(source)) == "valid utf8_char string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect((dest_a == ref(source)) == "valid utf8_char string"_b) << fatal; - }; - - "to_utf8"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf8_char string"_b) << fatal; - expect(chars::length(source) == value(source.size())) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf8 string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf8 string"_b) << fatal; - }; - - "to_utf16_le"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf8_char string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf16_le string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf16_le string"_b) << fatal; - }; - - "to_utf16_be"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf8_char string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf16_be string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf16_be string"_b) << fatal; - }; - - "to_utf32"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf8_char string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf32 string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf32 string"_b) << fatal; - }; - } - - config().output_level = old_level; - }; - - GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"chars.utf8"> chars_utf8 = [] - { - using namespace unit_test; - using namespace literals; - - const auto old_level = std::exchange(config().output_level, OutputLevel::NONE); - - numeric::Random random; - const auto generator = [&random](auto& source) mutable -> std::size_t - { - unsigned v; - do - { - v = random.get(0, 0x0010'ffef); - } while ( - // skip surrogate pairs - (v >= 0xd800 and v <= 0xdfff) // or - // // skip non-characters - // (v >= 0xfdd0 and v <= 0xfdef) or (v & 0xfffe) == 0xfffe or - // // skip control characters - // (v <= 0x001f) or (v >= 0x007f and v <= 0x009f) or - // // skip special characters - // (v >= 0xfff0 and v <= 0xfff8) or - // // skip tag characters and variation selector supplement - // (v >= 0x000e'0000 and v <= 0x000e'01ef) or - // // skip private use areas - // (v >= 0x000f'0000) or (v >= 0xe000 and v <= 0xf8ff) or - // // skip ideographic description characters - // (v >= 0x2ff0 and v <= 0x2fff) - ); - - if (v < 0x0080) - { - source.push_back(static_cast(v)); - return 1; - } - else if (v < 0x0800) - { - source.push_back(static_cast(0xc0 | (v >> 6))); - source.push_back(static_cast(0x80 | (v & 0x3f))); - return 2; - } - else if (v < 0x0001'0000) - { - source.push_back(static_cast(0xe0 | (v >> 12))); - source.push_back(static_cast(0x80 | ((v >> 6) & 0x3f))); - source.push_back(static_cast(0x80 | (v & 0x3f))); - return 3; - } - else - { - source.push_back(static_cast(0xf0 | (v >> 18))); - source.push_back(static_cast(0x80 | ((v >> 12) & 0x3f))); - source.push_back(static_cast(0x80 | ((v >> 6) & 0x3f))); - source.push_back(static_cast(0x80 | (v & 0x3f))); - return 4; - } - }; - const auto generator_char_only = [&random](auto& source) mutable -> std::size_t - { - source.push_back(static_cast(random.get() & std::numeric_limits::max())); - return 1; - }; - - constexpr std::size_t trials = 1000; - for (std::size_t i = 0; i < trials; ++i) - { - "to_ascii"_test = [source = make_source(generator_char_only, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf8 string"_b) << fatal; - // fixme: MSVC ICE HERE! - expect(chars::length(source) == value(source.size())) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid ascii string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid ascii string"_b) << fatal; - }; - - "to_utf8_char"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf8 string"_b) << fatal; - // fixme: MSVC ICE HERE! - expect(chars::length(source) == value(source.size())) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf8_char string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf8_char string"_b) << fatal; - }; - - "to_utf8"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf8 string"_b) << fatal; - // fixme: MSVC ICE HERE! - expect(chars::length(source) == value(source.size())) << fatal; - - const auto dest = chars::convert(source); - expect((dest == ref(source)) == "valid utf8 string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect((dest_a == ref(source)) == "valid utf8 string"_b) << fatal; - }; - - "to_utf16_le"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf8 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf16_le string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf16_le string"_b) << fatal; - }; - - "to_utf16_be"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf8 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf16_be string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf16_be string"_b) << fatal; - }; - - "to_utf32"_test = [source = make_source(generator, random.get(0, 65535))] - { - using operators::operator==; - expect(chars::validate(source) == "valid utf8 string"_b) << fatal; - - const auto dest = chars::convert(source); - expect(chars::validate(dest) == "valid utf32 string"_b) << fatal; - - const auto dest_a = chars::convert(source); - expect(chars::validate(dest_a) == "valid utf32 string"_b) << fatal; - }; - } - - config().output_level = old_level; - }; -} diff --git a/unit_test/src/concurrency/queue.cpp b/unit_test/src/concurrency/queue.cpp new file mode 100644 index 00000000..05712dfe --- /dev/null +++ b/unit_test/src/concurrency/queue.cpp @@ -0,0 +1,297 @@ +#include +#include +#include +#include +#include + +#include +// concurrency::queue +#include + +using namespace gal::prometheus; + +namespace +{ + GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"concurrency.queue"> _ = [] + { + using namespace unit_test; + using namespace concurrency; + + constexpr static std::size_t producers_count = 1; + constexpr static std::size_t consumers_count = 2; + constexpr static std::size_t queue_capacity = 1024; + + "atomic_queue"_test = [] + { + using production_type = std::uint32_t; + using sum_production_type = std::uint64_t; + + constexpr static production_type nil_value = std::numeric_limits::max(); + constexpr static production_type terminate_product = 42; + + constexpr static sum_production_type production_per_producer = 1'000'000; + [[maybe_unused]] constexpr static auto expected_total_production = + static_cast(static_cast(production_per_producer + terminate_product + 1) / + 2 * + (production_per_producer - terminate_product) * + producers_count + ); + + const auto do_create_consumers = []( + auto& queue, + std::span sums, + std::span consumers + ) noexcept -> void + { + std::ranges::for_each( + std::views::zip(sums, consumers), + [&queue](const std::tuple& pack) noexcept -> void + { + auto& [sum, consumer] = pack; + + consumer = std::thread{ + [&queue, &sum]() noexcept -> void + { + std::uint64_t total = 0; + while (true) + { + const auto current = queue.pop(); + if (current == terminate_product) + { + break; + } + + total += current; + } + + sum = total; + } + }; + } + ); + }; + + const auto do_create_producers = []( + auto& queue, + std::span producers + ) noexcept -> void + { + std::ranges::for_each( + producers, + [&queue](std::thread& producer) noexcept -> void + { + producer = std::thread{ + [&queue]() noexcept -> void + { + for (auto n = production_per_producer; n != terminate_product; --n) + { + queue.push(n); + } + } + }; + } + ); + }; + + const auto do_start_and_check = []( + auto& queue, + std::span sums, + std::span consumers, + std::span producers + ) noexcept -> void + { + std::ranges::for_each(producers, &std::thread::join); + std::ranges::for_each(std::views::iota(0) | std::views::take(consumers_count), [&queue](auto) { queue.push(terminate_product); }); + std::ranges::for_each(consumers, &std::thread::join); + + const auto total = std::ranges::fold_left( + sums, + std::uint64_t{0}, + [](const auto t, const auto c) noexcept -> std::uint64_t + { + return t + c; + } + ); + + expect(total == value(expected_total_production)); + }; + + "fixed_atomic_queue"_test = [ + do_create_consumers, + do_create_producers, + do_start_and_check + ] + { + using queue_type = FixedAtomicQueue; + queue_type queue{}; + + std::uint64_t sums[consumers_count]; + std::thread consumers[consumers_count]; + std::thread producers[producers_count]; + + do_create_consumers(queue, sums, consumers); + do_create_producers(queue, producers); + do_start_and_check(queue, sums, consumers, producers); + }; + + "dynamic_atomic_queue"_test = [ + do_create_consumers, + do_create_producers, + do_start_and_check + ] + { + using queue_type = DynamicAtomicQueue; + queue_type queue{queue_capacity}; + + std::uint64_t sums[consumers_count]; + std::thread consumers[consumers_count]; + std::thread producers[producers_count]; + + do_create_consumers(queue, sums, consumers); + do_create_producers(queue, producers); + do_start_and_check(queue, sums, consumers, producers); + }; + }; + + "queue"_test = [] + { + struct production_type + { + std::string name; + std::uint32_t id; + + [[nodiscard]] constexpr auto operator==(const production_type& other) const noexcept -> bool = default; + }; + using sum_production_type = std::uint64_t; + + constexpr static std::uint32_t terminate_product_id = 42; + const production_type terminate_product{.name = "", .id = terminate_product_id}; + + constexpr static sum_production_type production_per_producer = 1'000'000; + [[maybe_unused]] constexpr static auto expected_total_production = + static_cast(static_cast(production_per_producer + terminate_product_id + 1) / + 2 * + (production_per_producer - terminate_product_id) * + producers_count + ); + + const auto do_create_consumers = [terminate_product]( + auto& queue, + std::span sums, + std::span consumers + ) noexcept -> void + { + std::ranges::for_each( + std::views::zip(sums, consumers), + [&queue, terminate_product](const std::tuple& pack) noexcept -> void + { + auto& [sum, consumer] = pack; + + consumer = std::thread{ + [&queue, &sum, terminate_product]() noexcept -> void + { + std::uint64_t total = 0; + while (true) + { + const auto current = queue.pop(); + if (current == terminate_product) + { + break; + } + + total += current.id; + } + + sum = total; + } + }; + } + ); + }; + + const auto do_create_producers = [terminate_product]( + auto& queue, + std::span producers + ) noexcept -> void + { + std::ranges::for_each( + producers, + [&queue, terminate_product](std::thread& producer) noexcept -> void + { + producer = std::thread{ + [&queue, terminate_product]() noexcept -> void + { + for (auto n = production_per_producer; n != terminate_product.id; --n) + { + queue.push(production_type{ + .name = std::format("{}", n), + .id = static_cast(n) + }); + } + } + }; + } + ); + }; + + const auto do_start_and_check = [terminate_product]( + auto& queue, + std::span sums, + std::span consumers, + std::span producers + ) noexcept -> void + { + std::ranges::for_each(producers, &std::thread::join); + std::ranges::for_each(std::views::iota(0) | std::views::take(consumers_count), [&queue, terminate_product](auto) { queue.push(terminate_product); }); + std::ranges::for_each(consumers, &std::thread::join); + + const auto total = std::ranges::fold_left( + sums, + std::uint64_t{0}, + [](const auto t, const auto c) noexcept -> std::uint64_t + { + return t + c; + } + ); + + expect(total == value(expected_total_production)); + }; + + "fixed_queue"_test = [ + do_create_consumers, + do_create_producers, + do_start_and_check + ] + { + using queue_type = FixedQueue; + queue_type queue{}; + + std::uint64_t sums[consumers_count]; + std::thread consumers[consumers_count]; + std::thread producers[producers_count]; + + do_create_consumers(queue, sums, consumers); + do_create_producers(queue, producers); + do_start_and_check(queue, sums, consumers, producers); + }; + + "dynamic_queue"_test = [ + do_create_consumers, + do_create_producers, + do_start_and_check + ] + { + using queue_type = DynamicQueue; + queue_type queue{queue_capacity}; + + std::uint64_t sums[consumers_count]; + std::thread consumers[consumers_count]; + std::thread producers[producers_count]; + + do_create_consumers(queue, sums, consumers); + do_create_producers(queue, producers); + do_start_and_check(queue, sums, consumers, producers); + }; + }; + }; +} diff --git a/unit_test/src/coroutine/generator.cpp b/unit_test/src/coroutine/generator.cpp index ce3a68c6..ea871565 100644 --- a/unit_test/src/coroutine/generator.cpp +++ b/unit_test/src/coroutine/generator.cpp @@ -1,31 +1,32 @@ -#include +#include +#include -import std; -import gal.prometheus.test; -import gal.prometheus.coroutine; +#include +// coroutine::generator +#include + +using namespace gal::prometheus; namespace { - using namespace gal::prometheus; - using namespace coroutine; - - GAL_PROMETHEUS_NO_DESTROY test::suite<"coroutine.generator"> _ = [] + GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"coroutine.generator"> _ = [] { - using namespace test; + using namespace unit_test; + using namespace coroutine; - ignore_pass / "0~10"_test = [] + "0~10"_test = [] { auto generator = []() -> Generator { for (int i = 0; i <= 10; ++i) { co_yield i; } }(); - for (int i = 0; + for (int i = 0; const auto each: generator) { - expect(each == as_i{i}) << fatal; + expect(each == value(i)) << fatal; ++i; } }; - "0~10 with exception"_test / ignore_pass = [] + "0~10 with exception"_test = [] { auto generator = []() -> Generator { @@ -39,10 +40,10 @@ namespace try { - for (int i = 0; + for (int i = 0; const auto each: generator) { - expect(each == as_i{i}) << fatal; + expect(each == value(i)) << fatal; ++i; } } @@ -55,10 +56,10 @@ namespace { auto generator = []() -> Generator> { for (int i = 0; i <= 10; ++i) { co_yield std::make_unique(i); } }(); - for (int i = 0; + for (int i = 0; const auto& each: generator) { - expect(ignore_pass % *each == as_i{i}) << fatal; + expect(*each == value(i)) << fatal; ++i; } } @@ -67,10 +68,10 @@ namespace { auto generator = []() -> Generator> { for (int i = 0; i <= 10; ++i) { co_yield std::make_unique(i); } }(); - for (int i = 0; + for (int i = 0; auto&& each: generator) { - expect(ignore_pass % *each == as_i{i}) << fatal; + expect(*each == value(i)) << fatal; ++i; } } @@ -88,16 +89,15 @@ namespace } }(); - for (int current = 0; + for (unsigned int i = 0; const auto each: generator) { - // silence -> avoid spam - expect(silence % each == as_i{current}) << fatal; - ++current; + expect(each == value(i)) << fatal; + ++i; - if (constexpr int max = 1024; - current > max) { break; } + if (constexpr unsigned int max = 1024; + i > max) { break; } } }; }; -}// namespace +} // namespace diff --git a/unit_test/src/coroutine/task.cpp b/unit_test/src/coroutine/task.cpp index 2d92a5a7..884385b3 100644 --- a/unit_test/src/coroutine/task.cpp +++ b/unit_test/src/coroutine/task.cpp @@ -1,22 +1,23 @@ -#include +#include +#include -import std; -import gal.prometheus.test; -import gal.prometheus.coroutine; +#include +// coroutine::task +#include + +using namespace gal::prometheus; namespace { - using namespace gal::prometheus; - using namespace coroutine; - // It's a good idea to read what's in the link before reading the code below. // https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rcoro-capture - GAL_PROMETHEUS_NO_DESTROY test::suite<"coroutine.task"> _ = [] + GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"coroutine.task"> _ = [] { - using namespace test; + using namespace unit_test; + using namespace coroutine; - ignore_pass / "void"_test = [] + "void"_test = [] { auto return_void = []() -> Task { co_return; }(); @@ -27,7 +28,7 @@ namespace expect(return_void.done() == "return_void.done()"_b) << fatal; }; - ignore_pass / "hello_world"_test = [] + "hello_world"_test = [] { using task_type = Task; @@ -50,10 +51,10 @@ namespace expect(world == "world"_s) << fatal; expect(say_hello.promise().result().empty() != "not say_hello.promise().result().empty()"_b) << fatal; - expect(say_world.promise().result().empty() == "say_world.promise().result().empty()"_b) << fatal;// NOLINT // use-after-move + expect(say_world.promise().result().empty() == "say_world.promise().result().empty()"_b) << fatal; // NOLINT // use-after-move }; - ignore_pass / "exception"_test = [] + "exception"_test = [] { using task_type = Task; @@ -72,19 +73,19 @@ namespace expect(throw_exception.promise().has_exception() == "throw_exception.promise().has_exception()"_b) << fatal; expect( - [&throw_exception] + [&throw_exception] + { + try { std::ignore = throw_exception.promise().result(); } + catch (const std::runtime_error& e) { - try { [[maybe_unused]] const auto& _ = throw_exception.promise().result(); } - catch (const std::runtime_error& e) - { - expect(e.what() == "exception raise!"_s) << fatal; - return true; - } - return false; - }() == "throw it!"_b) << fatal; + expect(e.what() == "exception raise!"_s) << fatal; + return true; + } + return false; + }() == "throw it!"_b) << fatal; }; - ignore_pass / "nested_task"_test = [] + "nested_task"_test = [] { constexpr static std::string_view m1{"inner task here~\n"}; constexpr static std::string_view m2{"outer task waiting...\n"}; @@ -109,10 +110,10 @@ namespace outer.resume(); - expect(message == as_s{std::string{m2}.append(m1).append(m3)}) << fatal; + expect(message == value(std::string{m2}.append(m1).append(m3))) << fatal; }; - ignore_pass / "suspend_task"_test = [] + "suspend_task"_test = [] { auto suspend_task = []() -> Task { @@ -140,7 +141,7 @@ namespace expect(suspend_task.promise().result() == 42_auto) << fatal; }; - ignore_pass / "coroutine_handle"_test = [] + "coroutine_handle"_test = [] { auto task_1 = []() -> Task { co_return 42; }(); @@ -161,4 +162,4 @@ namespace expect(task_2.promise().result() == "42"_s) << fatal; }; }; -}// namespace +} // namespace diff --git a/unit_test/src/draw/CMakeLists.txt b/unit_test/src/draw/CMakeLists.txt new file mode 100644 index 00000000..f61b8ee1 --- /dev/null +++ b/unit_test/src/draw/CMakeLists.txt @@ -0,0 +1,10 @@ +if (${PROJECT_NAME_PREFIX}PLATFORM_WINDOWS) + add_subdirectory(dx11) + add_subdirectory(dx12) +endif (${PROJECT_NAME_PREFIX}PLATFORM_WINDOWS) + +#find_package(Vulkan) +# +#if (Vulkan_FOUND) +# add_subdirectory(vulkan) +#endif (Vulkan_FOUND) diff --git a/unit_test/src/gui/glfw_callback_handler.cpp b/unit_test/src/draw/common/glfw_callback_handler.cpp similarity index 73% rename from unit_test/src/gui/glfw_callback_handler.cpp rename to unit_test/src/draw/common/glfw_callback_handler.cpp index c3c37de3..1b162b05 100644 --- a/unit_test/src/gui/glfw_callback_handler.cpp +++ b/unit_test/src/draw/common/glfw_callback_handler.cpp @@ -1,13 +1,9 @@ -#if GAL_PROMETHEUS_USE_MODULE -import gal.prometheus; -#else -#include -#endif +#include +#include #include -#include -#include +#include namespace { @@ -25,13 +21,10 @@ namespace enum class MouseButton { LEFT = GLFW_MOUSE_BUTTON_LEFT, - MIDDLE = GLFW_MOUSE_BUTTON_MIDDLE, RIGHT = GLFW_MOUSE_BUTTON_RIGHT, - X1 = 3, - X2 = 4, - X3 = 5, - X4 = 6, - X5 = 7, + MIDDLE = GLFW_MOUSE_BUTTON_MIDDLE, + X1, + X2, }; enum class MouseAction @@ -110,6 +103,44 @@ namespace }; } +template<> +struct meta::user_defined::enum_name_policy +{ + constexpr static auto value = EnumNamePolicy::WITH_SCOPED_NAME; +}; + +template<> +struct meta::user_defined::enum_name_policy +{ + constexpr static auto value = EnumNamePolicy::WITH_SCOPED_NAME; +}; + +template<> +struct meta::user_defined::enum_name_policy +{ + constexpr static auto value = EnumNamePolicy::WITH_SCOPED_NAME; +}; + +template<> +struct meta::user_defined::enum_name_policy +{ + constexpr static auto value = EnumNamePolicy::WITH_SCOPED_NAME; +}; + +template<> +struct meta::user_defined::enum_name_policy +{ + constexpr static auto value = EnumNamePolicy::WITH_SCOPED_NAME; +}; + +template<> +struct meta::user_defined::enum_name_policy +{ + constexpr static auto value = EnumNamePolicy::WITH_SCOPED_NAME; +}; + +// io::DeviceEventQueue g_device_event_queue; + auto glfw_callback_setup(GLFWwindow& w) -> void { static auto callback_window_focus = [](GLFWwindow* window, const int focused) @@ -139,6 +170,8 @@ auto glfw_callback_setup(GLFWwindow& w) -> void { std::println(stdout, "[CURSOR]: window: 0x{:x}, x: {}, y: {}", reinterpret_cast(window), x, y); + // g_device_event_queue.mouse_move(static_cast(x), static_cast(y)); + if (g_glfw_callback_window_cursor_position) { g_glfw_callback_window_cursor_position(window, x, y); @@ -155,6 +188,40 @@ auto glfw_callback_setup(GLFWwindow& w) -> void meta::to_string(static_cast(mods)) ); + // const auto status = static_cast(action) == MouseAction::PRESS ? io::MouseButtonStatus::PRESS : io::MouseButtonStatus::RELEASE; + // switch (static_cast(button)) + // { + // case MouseButton::LEFT: + // { + // g_device_event_queue.mouse_button(io::MouseButton::LEFT, status); + // break; + // } + // case MouseButton::MIDDLE: + // { + // g_device_event_queue.mouse_button(io::MouseButton::MIDDLE, status); + // break; + // } + // case MouseButton::RIGHT: + // { + // g_device_event_queue.mouse_button(io::MouseButton::RIGHT, status); + // break; + // } + // case MouseButton::X1: + // { + // g_device_event_queue.mouse_button(io::MouseButton::X1, status); + // break; + // } + // case MouseButton::X2: + // { + // g_device_event_queue.mouse_button(io::MouseButton::X2, status); + // break; + // } + // default: + // { + // break; + // } + // } + if (g_glfw_callback_window_mouse_button) { g_glfw_callback_window_mouse_button(window, button, action, mods); @@ -164,6 +231,8 @@ auto glfw_callback_setup(GLFWwindow& w) -> void { std::println(stdout, "[MOUSE SCROLL]: window: 0x{:x}, x: {}, y: {}", reinterpret_cast(window), x, y); + // g_device_event_queue.mouse_wheel(static_cast(x), static_cast(y)); + if (g_glfw_callback_window_scroll) { g_glfw_callback_window_scroll(window, x, y); @@ -173,9 +242,10 @@ auto glfw_callback_setup(GLFWwindow& w) -> void { std::println( stdout, - "[KEYBOARD]: window: 0x{:x}, key_code: [{}], scan_code: {}, action: {}, mods: {}", + "[KEYBOARD]: window: 0x{:x}, key_code: [{}]({}), scan_code: {}, action: {}, mods: {}", reinterpret_cast(window), meta::to_string(static_cast(key_code)), + key_code, scan_code, meta::to_string(static_cast(action)), meta::to_string(static_cast(mods)) diff --git a/unit_test/src/draw/common/print_time.hpp b/unit_test/src/draw/common/print_time.hpp new file mode 100644 index 00000000..6729416c --- /dev/null +++ b/unit_test/src/draw/common/print_time.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include +#include +#include + +inline auto print_time(const std::source_location& location = std::source_location::current()) noexcept -> void +{ + std::println(stdout, "[{:%F %T}] {}", std::chrono::floor(std::chrono::system_clock::now()), location.function_name()); +} diff --git a/unit_test/src/draw/dx11/CMakeLists.txt b/unit_test/src/draw/dx11/CMakeLists.txt new file mode 100644 index 00000000..6594cbea --- /dev/null +++ b/unit_test/src/draw/dx11/CMakeLists.txt @@ -0,0 +1,45 @@ +project( + prometheus-draw-dx11 + LANGUAGES CXX +) + +add_executable( + ${PROJECT_NAME} + + ${PROJECT_SOURCE_DIR}/../common/glfw_callback_handler.cpp + ${PROJECT_SOURCE_DIR}/backend.cpp + ${PROJECT_SOURCE_DIR}/main.cpp +) + +target_compile_features( + ${PROJECT_NAME} + PUBLIC + cxx_std_23 +) + +target_link_libraries( + ${PROJECT_NAME} + PUBLIC + prometheus +) + +# STB +include(${${PROJECT_NAME_PREFIX}ROOT_PATH_EXTERNAL_LIBRARY}/stb/stb.cmake) +cmake_language( + CALL + ${PROJECT_NAME_PREFIX}ATTACH_EXTERNAL_STB +) + +# GLFW +include(${${PROJECT_NAME_PREFIX}ROOT_PATH_EXTERNAL_LIBRARY}/glfw/glfw.cmake) +cmake_language( + CALL + ${PROJECT_NAME_PREFIX}ATTACH_EXTERNAL_GLFW +) + +target_compile_definitions( + ${PROJECT_NAME} + PUBLIC + + ASSETS_PATH_PIC="${ASSETS_PATH_PIC}" +) \ No newline at end of file diff --git a/unit_test/src/gui/backend_dx11.cpp b/unit_test/src/draw/dx11/backend.cpp similarity index 67% rename from unit_test/src/gui/backend_dx11.cpp rename to unit_test/src/draw/dx11/backend.cpp index 06a6e501..26258877 100644 --- a/unit_test/src/gui/backend_dx11.cpp +++ b/unit_test/src/draw/dx11/backend.cpp @@ -1,5 +1,5 @@ -#include "def.hpp" -#include "dx_error_handler.hpp" +#include "../win/def.hpp" +#include "../common/print_time.hpp" #include #include @@ -9,12 +9,10 @@ #define STB_IMAGE_IMPLEMENTATION #include -namespace -{ - using Microsoft::WRL::ComPtr; +#include - using namespace gal::prometheus; -} // namespace +using Microsoft::WRL::ComPtr; +using namespace gal::prometheus; extern ComPtr g_device; extern ComPtr g_device_immediate_context; @@ -26,8 +24,7 @@ extern double g_last_time; extern std::uint64_t g_frame_count; extern float g_fps; -extern std::shared_ptr g_draw_list_shared_data; -extern gui::DrawList g_draw_list; +// extern io::DeviceEventQueue g_device_event_queue; namespace { @@ -56,7 +53,14 @@ namespace ComPtr g_additional_picture_texture = nullptr; - [[nodiscard]] auto load_texture(const std::uint8_t* texture_data, const std::uint32_t texture_width, const std::uint32_t texture_height, ComPtr& out_srv) -> bool + draw::Window g_window{"Window 1", {100, 100, 640, 480}}; + + [[nodiscard]] auto load_texture( + const std::uint8_t* texture_data, + const std::uint32_t texture_width, + const std::uint32_t texture_height, + ComPtr& out_srv + ) -> bool { assert(texture_data); assert(texture_width != 0 and texture_height != 0); @@ -105,9 +109,15 @@ auto prometheus_init() -> void // { print_time(); - using functional::operators::operator|; - g_draw_list.draw_list_flag(gui::DrawListFlag::ANTI_ALIASED_LINE | gui::DrawListFlag::ANTI_ALIASED_FILL); - g_draw_list.shared_data(g_draw_list_shared_data); + const auto glyph_range = i18n::RangeBuilder{}.simplified_chinese_common().range(); + const auto font_option = + draw::Font::option() + .path(R"(C:\Windows\Fonts\msyh.ttc)") + .glyph_ranges(i18n::RangeBuilder{}.simplified_chinese_common().range()) + .pixel_height(18); + + auto font = std::make_shared(); + auto font_texture = font->load(font_option); // Create the blending setup { @@ -229,7 +239,7 @@ auto prometheus_init() -> void // FAILED(result)) { std::println( - std::cerr, + stderr, "D3DCompile failed: {} -- at {}:{}", static_cast(error_message->GetBufferPointer()), std::source_location::current().file_name(), @@ -331,7 +341,7 @@ auto prometheus_init() -> void // FAILED(result)) { std::println( - std::cerr, + stderr, "D3DCompile failed: {} -- at {}:{}", static_cast(error_message->GetBufferPointer()), std::source_location::current().file_name(), @@ -368,14 +378,15 @@ auto prometheus_init() -> void // // Load default font texture { - const auto& default_font = g_draw_list_shared_data->get_default_font(); - const auto font_data = default_font.texture_data.get(); - const auto font_width = default_font.texture_size.width; - const auto font_height = default_font.texture_size.height; - const auto load_font_texture_result = load_texture(reinterpret_cast(font_data), font_width, font_height, g_font_texture); + [[maybe_unused]] const auto load_font_texture_result = load_texture( + reinterpret_cast(font_texture.data().get()), + font_texture.width(), + font_texture.height(), + g_font_texture + ); assert(load_font_texture_result); - g_draw_list_shared_data->get_default_font().texture_id = reinterpret_cast(g_font_texture.Get()); + font_texture.bind(reinterpret_cast(g_font_texture.Get())); } // Load additional picture texture @@ -386,106 +397,159 @@ auto prometheus_init() -> void // auto* data = stbi_load(ASSETS_PATH_PIC, &image_width, &image_height, nullptr, 4); assert(data); - const auto load_additional_texture_result = load_texture(data, image_width, image_height, g_additional_picture_texture); + [[maybe_unused]] const auto load_additional_texture_result = load_texture( + data, + image_width, + image_height, + g_additional_picture_texture + ); assert(load_additional_texture_result); stbi_image_free(data); } + + GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(font->loaded()); + + auto& context = draw::Context::instance(); + auto& draw_list = g_window.test_draw_list(); + + context.set_default_font(font); + context.test_set_window(g_window); + + draw_list.draw_list_flag(draw::DrawListFlag::ANTI_ALIASED_LINE | draw::DrawListFlag::ANTI_ALIASED_LINE_USE_TEXTURE | draw::DrawListFlag::ANTI_ALIASED_FILL); + draw_list.reset(); + + g_window.test_init(); } auto prometheus_new_frame() -> void // { - g_draw_list.reset(); - g_draw_list.push_clip_rect({0, 0},{static_cast(g_window_width), static_cast(g_window_height)},false); + auto& draw_list = g_window.test_draw_list(); + + draw_list.reset(); + draw_list.push_clip_rect({0, 0}, {static_cast(g_window_width), static_cast(g_window_height)}, false); } auto prometheus_render() -> void { - g_draw_list.text(24.f, {10, 10}, primitive::colors::blue, std::format("FPS: {:.3f}", g_fps)); - - g_draw_list.text(24.f, {50, 50}, primitive::colors::red, "The quick brown fox jumps over the lazy dog.\nHello world!\n你好世界!\n"); - - g_draw_list.line({200, 100}, {200, 300}, primitive::colors::red); - g_draw_list.line({100, 200}, {300, 200}, primitive::colors::red); - - g_draw_list.rect({100, 100}, {300, 300}, primitive::colors::blue); - g_draw_list.rect({150, 150}, {250, 250}, primitive::colors::blue, 30); - - g_draw_list.triangle({120, 120}, {120, 150}, {150, 120}, primitive::colors::green); - g_draw_list.triangle_filled({130, 130}, {130, 150}, {150, 130}, primitive::colors::red); - - g_draw_list.rect_filled({300, 100}, {400, 200}, primitive::colors::pink); - g_draw_list.rect_filled({300, 200}, {400, 300}, primitive::colors::pink, 20); - g_draw_list.rect_filled({300, 300}, {400, 400}, primitive::colors::pink, primitive::colors::gold, primitive::colors::azure, primitive::colors::lavender); - - g_draw_list.quadrilateral({100, 500}, {200, 500}, {250, 550}, {50, 550}, primitive::colors::red); - g_draw_list.quadrilateral_filled({100, 500}, {200, 500}, {250, 450}, {50, 450}, primitive::colors::red); - - g_draw_list.circle({100, 600}, 50, primitive::colors::green); - g_draw_list.circle({200, 600}, 50, primitive::colors::red, 8); - g_draw_list.circle_filled({100, 700}, 50, primitive::colors::green); - g_draw_list.circle_filled({200, 700}, 50, primitive::colors::red, 8); - - g_draw_list.ellipse({500, 100}, {50, 70}, std::numbers::pi_v * .35f, primitive::colors::red, 8); - g_draw_list.ellipse_filled({500, 200}, {50, 70}, std::numbers::pi_v * -.35f, primitive::colors::red, 8); - g_draw_list.ellipse({600, 100}, {50, 70}, std::numbers::pi_v * .35f, primitive::colors::red, 16); - g_draw_list.ellipse_filled({600, 200}, {50, 70}, std::numbers::pi_v * -.35f, primitive::colors::red, 16); - g_draw_list.ellipse({700, 100}, {50, 70}, std::numbers::pi_v * .35f, primitive::colors::red, 24); - g_draw_list.ellipse_filled({700, 200}, {50, 70}, std::numbers::pi_v * -.35f, primitive::colors::red, 24); - g_draw_list.ellipse({800, 100}, {50, 70}, std::numbers::pi_v * .35f, primitive::colors::red); - g_draw_list.ellipse_filled({800, 200}, {50, 70}, std::numbers::pi_v * -.35f, primitive::colors::red); - - g_draw_list.circle_filled({500, 300}, 5, primitive::colors::red); - g_draw_list.circle_filled({600, 350}, 5, primitive::colors::red); - g_draw_list.circle_filled({450, 500}, 5, primitive::colors::red); - g_draw_list.circle_filled({550, 550}, 5, primitive::colors::red); - g_draw_list.bezier_cubic({500, 300}, {600, 350}, {450, 500}, {550, 550}, primitive::colors::green); - - g_draw_list.circle_filled({600, 300}, 5, primitive::colors::red); - g_draw_list.circle_filled({700, 350}, 5, primitive::colors::red); - g_draw_list.circle_filled({550, 500}, 5, primitive::colors::red); - g_draw_list.circle_filled({650, 550}, 5, primitive::colors::red); - g_draw_list.bezier_cubic({600, 300}, {700, 350}, {550, 500}, {650, 550}, primitive::colors::green, 5); - - g_draw_list.circle_filled({500, 600}, 5, primitive::colors::red); - g_draw_list.circle_filled({600, 650}, 5, primitive::colors::red); - g_draw_list.circle_filled({450, 800}, 5, primitive::colors::red); - g_draw_list.bezier_quadratic({500, 600}, {600, 650}, {450, 800}, primitive::colors::green); - - g_draw_list.circle_filled({600, 600}, 5, primitive::colors::red); - g_draw_list.circle_filled({700, 650}, 5, primitive::colors::red); - g_draw_list.circle_filled({550, 800}, 5, primitive::colors::red); - g_draw_list.bezier_quadratic({600, 600}, {700, 650}, {550, 800}, primitive::colors::green, 5); - - // push bound - // [800,350] => [1000, 550] (200 x 200) - g_draw_list.push_clip_rect({800, 350}, {1000, 550}, true); - g_draw_list.rect({800, 350}, {1000, 550}, primitive::colors::red); - // out-of-bound - g_draw_list.triangle_filled({700, 250}, {900, 400}, {850, 450}, primitive::colors::green); - // in-bound - g_draw_list.triangle_filled({900, 450}, {1000, 450}, {950, 550}, primitive::colors::blue); - // pop bound - g_draw_list.pop_clip_rect(); - - g_draw_list.triangle_filled({800, 450}, {700, 750}, {850, 800}, primitive::colors::gold); - - // font texture - g_draw_list.image(g_draw_list_shared_data->get_default_font().texture_id, {900, 20, 1200, 320}); - g_draw_list.image_rounded(reinterpret_cast(g_additional_picture_texture.Get()), {900, 350, 1200, 650}, 10); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - g_draw_list.bind_debug_info(); - #endif + g_window.test_init(); + if (g_window.draw_button("Button")) + { + std::println(stdout, "Button!"); + } + + g_window.draw_text("Hello1"); + g_window.draw_text("World1"); + + g_window.draw_text("Hello2"); + g_window.layout_same_line(); + g_window.draw_text("World2"); + + { + static bool checked = false; + if (const auto new_status = g_window.draw_checkbox("Checkbox1", checked); + new_status != checked) + { + checked = new_status; + std::println(stdout, "Checkbox1!"); + } + } + { + static bool checked = true; + if (const auto new_status = g_window.draw_checkbox("Checkbox2", checked); + new_status != checked) + { + checked = new_status; + std::println(stdout, "Checkbox2!"); + } + } + + // auto& draw_list = g_window.test_draw_list(); + + // draw_list.text(24.f, {10, 10}, primitive::colors::blue, std::format("FPS: {:.3f}", g_fps)); + // + // draw_list.text(18.f, {50, 50}, primitive::colors::red, "The quick brown fox jumps over the lazy dog.\nHello world!\n你好世界!\n"); + + // draw_list.line({200, 100}, {200, 300}, primitive::colors::red); + // draw_list.line({100, 200}, {300, 200}, primitive::colors::red); + // + // draw_list.rect({100, 100}, {300, 300}, primitive::colors::blue); + // draw_list.rect({150, 150}, {250, 250}, primitive::colors::blue, 30); + // + // draw_list.triangle({120, 120}, {120, 150}, {150, 120}, primitive::colors::green); + // draw_list.triangle_filled({130, 130}, {130, 150}, {150, 130}, primitive::colors::red); + // + // draw_list.rect_filled({300, 100}, {400, 200}, primitive::colors::pink); + // draw_list.rect_filled({300, 200}, {400, 300}, primitive::colors::pink, 20); + // draw_list.rect_filled({300, 300}, {400, 400}, primitive::colors::pink, primitive::colors::gold, primitive::colors::azure, primitive::colors::lavender); + // + // draw_list.quadrilateral({100, 500}, {200, 500}, {250, 550}, {50, 550}, primitive::colors::red); + // draw_list.quadrilateral_filled({100, 500}, {200, 500}, {250, 450}, {50, 450}, primitive::colors::red); + // + // draw_list.circle({100, 600}, 50, primitive::colors::green); + // draw_list.circle({200, 600}, 50, primitive::colors::red, 8); + // draw_list.circle_filled({100, 700}, 50, primitive::colors::green); + // draw_list.circle_filled({200, 700}, 50, primitive::colors::red, 8); + // + // draw_list.ellipse({500, 100}, {50, 70}, std::numbers::pi_v * .35f, primitive::colors::red, 8); + // draw_list.ellipse_filled({500, 200}, {50, 70}, std::numbers::pi_v * -.35f, primitive::colors::red, 8); + // draw_list.ellipse({600, 100}, {50, 70}, std::numbers::pi_v * .35f, primitive::colors::red, 16); + // draw_list.ellipse_filled({600, 200}, {50, 70}, std::numbers::pi_v * -.35f, primitive::colors::red, 16); + // draw_list.ellipse({700, 100}, {50, 70}, std::numbers::pi_v * .35f, primitive::colors::red, 24); + // draw_list.ellipse_filled({700, 200}, {50, 70}, std::numbers::pi_v * -.35f, primitive::colors::red, 24); + // draw_list.ellipse({800, 100}, {50, 70}, std::numbers::pi_v * .35f, primitive::colors::red); + // draw_list.ellipse_filled({800, 200}, {50, 70}, std::numbers::pi_v * -.35f, primitive::colors::red); + // + // draw_list.circle_filled({500, 300}, 5, primitive::colors::red); + // draw_list.circle_filled({600, 350}, 5, primitive::colors::red); + // draw_list.circle_filled({450, 500}, 5, primitive::colors::red); + // draw_list.circle_filled({550, 550}, 5, primitive::colors::red); + // draw_list.bezier_cubic({500, 300}, {600, 350}, {450, 500}, {550, 550}, primitive::colors::green); + // + // draw_list.circle_filled({600, 300}, 5, primitive::colors::red); + // draw_list.circle_filled({700, 350}, 5, primitive::colors::red); + // draw_list.circle_filled({550, 500}, 5, primitive::colors::red); + // draw_list.circle_filled({650, 550}, 5, primitive::colors::red); + // draw_list.bezier_cubic({600, 300}, {700, 350}, {550, 500}, {650, 550}, primitive::colors::green, 5); + // + // draw_list.circle_filled({500, 600}, 5, primitive::colors::red); + // draw_list.circle_filled({600, 650}, 5, primitive::colors::red); + // draw_list.circle_filled({450, 800}, 5, primitive::colors::red); + // draw_list.bezier_quadratic({500, 600}, {600, 650}, {450, 800}, primitive::colors::green); + // + // draw_list.circle_filled({600, 600}, 5, primitive::colors::red); + // draw_list.circle_filled({700, 650}, 5, primitive::colors::red); + // draw_list.circle_filled({550, 800}, 5, primitive::colors::red); + // draw_list.bezier_quadratic({600, 600}, {700, 650}, {550, 800}, primitive::colors::green, 5); + // + // // push bound + // // [800,350] => [1000, 550] (200 x 200) + // draw_list.push_clip_rect({800, 350}, {1000, 550}, true); + // draw_list.rect({800, 350}, {1000, 550}, primitive::colors::red); + // // out-of-bound + // draw_list.triangle_filled({700, 250}, {900, 400}, {850, 450}, primitive::colors::green); + // // in-bound + // draw_list.triangle_filled({900, 450}, {1000, 450}, {950, 550}, primitive::colors::blue); + // // pop bound + // draw_list.pop_clip_rect(); + // + // draw_list.triangle_filled({800, 450}, {700, 750}, {850, 800}, primitive::colors::gold); + // + // // font + // draw_list.image(draw::Context::instance().font().texture_id(), {900, 20, 300, 300}); + // // image + // draw_list.image_rounded(reinterpret_cast(g_additional_picture_texture.Get()), {900, 350, 300, 300}, 10); } auto prometheus_draw() -> void { + auto& draw_list = g_window.test_draw_list(); + auto& [this_frame_index_buffer, this_frame_index_count, this_frame_vertex_buffer, this_frame_vertex_count] = render_buffer; - const auto command_list = g_draw_list.command_list(); - const auto vertex_list = g_draw_list.vertex_list(); - const auto index_list = g_draw_list.index_list(); + const auto command_list = draw_list.command_list(); + const auto vertex_list = draw_list.vertex_list(); + const auto index_list = draw_list.index_list(); if (not this_frame_vertex_buffer or vertex_list.size() > this_frame_vertex_count) { @@ -493,7 +557,7 @@ auto prometheus_draw() -> void this_frame_vertex_count = static_cast(vertex_list.size()) + 5000; const D3D11_BUFFER_DESC buffer_desc{ - .ByteWidth = static_cast(this_frame_vertex_count * sizeof(gui::DrawList::vertex_type)), + .ByteWidth = static_cast(this_frame_vertex_count * sizeof(draw::DrawList::vertex_type)), .Usage = D3D11_USAGE_DYNAMIC, .BindFlags = D3D11_BIND_VERTEX_BUFFER, .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE, @@ -508,7 +572,7 @@ auto prometheus_draw() -> void this_frame_index_count = static_cast(index_list.size()) + 10000; const D3D11_BUFFER_DESC buffer_desc{ - .ByteWidth = static_cast(this_frame_index_count * sizeof(gui::DrawList::index_type)), + .ByteWidth = static_cast(this_frame_index_count * sizeof(draw::DrawList::index_type)), .Usage = D3D11_USAGE_DYNAMIC, .BindFlags = D3D11_BIND_INDEX_BUFFER, .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE, @@ -531,7 +595,7 @@ auto prometheus_draw() -> void std::ranges::transform( vertex_list, mapped_vertex, - [](const gui::DrawList::vertex_type& vertex) -> d3d_vertex_type + [](const draw::DrawList::vertex_type& vertex) -> d3d_vertex_type { // return { // .position = {vertex.position.x, vertex.position.y}, @@ -616,7 +680,7 @@ auto prometheus_draw() -> void const D3D11_RECT rect{static_cast(point.x), static_cast(point.y), static_cast(point.x + extent.width), static_cast(point.y + extent.height)}; g_device_immediate_context->RSSetScissorRects(1, &rect); - GAL_PROMETHEUS_ERROR_DEBUG_ASSUME(texture != 0, "push_texture_id when create texture view"); + assert(texture != 0 and "push_texture_id when create texture view"); ID3D11ShaderResourceView* textures[]{reinterpret_cast(texture)}; // NOLINT(performance-no-int-to-ptr) g_device_immediate_context->PSSetShaderResources(0, 1, textures); diff --git a/unit_test/src/gui/main_dx11.cpp b/unit_test/src/draw/dx11/main.cpp similarity index 94% rename from unit_test/src/gui/main_dx11.cpp rename to unit_test/src/draw/dx11/main.cpp index 343d3e53..c68ea904 100644 --- a/unit_test/src/gui/main_dx11.cpp +++ b/unit_test/src/draw/dx11/main.cpp @@ -1,10 +1,9 @@ -#include "def.hpp" -#include "dx_error_handler.hpp" +#include "../win/def.hpp" +#include "../common/print_time.hpp" #include #include #include -#include #include #define GLFW_EXPOSE_NATIVE_WIN32 @@ -16,11 +15,7 @@ #pragma comment(lib, "d3dcompiler.lib") #pragma comment(lib, "dxgi.lib") -namespace -{ - using namespace gal::prometheus; - using Microsoft::WRL::ComPtr; -} +using Microsoft::WRL::ComPtr; ComPtr g_device = nullptr; ComPtr g_device_immediate_context = nullptr; @@ -32,9 +27,6 @@ double g_last_time = 0; std::uint64_t g_frame_count = 0; float g_fps = 0; -auto g_draw_list_shared_data = std::make_shared(); -gui::DrawList g_draw_list; - extern auto glfw_callback_setup(GLFWwindow& w) -> void; extern auto prometheus_init() -> void; @@ -101,9 +93,6 @@ int main(int, char**) return -1; } - const auto range = gui::glyph_range_simplified_chinese_common(); - g_draw_list_shared_data->set_default_font(gui::load_font(R"(C:\Windows\Fonts\msyh.ttc)", 18, range)); - // Setup Platform/Renderer backends win32_init(*window); d3d_init(); diff --git a/unit_test/src/draw/dx12/CMakeLists.txt b/unit_test/src/draw/dx12/CMakeLists.txt new file mode 100644 index 00000000..4741bfcf --- /dev/null +++ b/unit_test/src/draw/dx12/CMakeLists.txt @@ -0,0 +1,45 @@ +project( + prometheus-draw-dx12 + LANGUAGES CXX +) + +add_executable( + ${PROJECT_NAME} + + ${PROJECT_SOURCE_DIR}/../common/glfw_callback_handler.cpp + ${PROJECT_SOURCE_DIR}/backend.cpp + ${PROJECT_SOURCE_DIR}/main.cpp +) + +target_compile_features( + ${PROJECT_NAME} + PUBLIC + cxx_std_23 +) + +target_link_libraries( + ${PROJECT_NAME} + PUBLIC + prometheus +) + +# STB +include(${${PROJECT_NAME_PREFIX}ROOT_PATH_EXTERNAL_LIBRARY}/stb/stb.cmake) +cmake_language( + CALL + ${PROJECT_NAME_PREFIX}ATTACH_EXTERNAL_STB +) + +# GLFW +include(${${PROJECT_NAME_PREFIX}ROOT_PATH_EXTERNAL_LIBRARY}/glfw/glfw.cmake) +cmake_language( + CALL + ${PROJECT_NAME_PREFIX}ATTACH_EXTERNAL_GLFW +) + +target_compile_definitions( + ${PROJECT_NAME} + PUBLIC + + ASSETS_PATH_PIC="${ASSETS_PATH_PIC}" +) \ No newline at end of file diff --git a/unit_test/src/gui/backend_dx12.cpp b/unit_test/src/draw/dx12/backend.cpp similarity index 95% rename from unit_test/src/gui/backend_dx12.cpp rename to unit_test/src/draw/dx12/backend.cpp index 2878bd1b..928c5d34 100644 --- a/unit_test/src/gui/backend_dx12.cpp +++ b/unit_test/src/draw/dx12/backend.cpp @@ -1,5 +1,5 @@ -#include "def.hpp" -#include "dx_error_handler.hpp" +#include "../win/def.hpp" +#include "../common/print_time.hpp" #include #include @@ -9,12 +9,10 @@ #define STB_IMAGE_IMPLEMENTATION #include -namespace -{ - using Microsoft::WRL::ComPtr; +#include - using namespace gal::prometheus; -} +using Microsoft::WRL::ComPtr; +using namespace gal::prometheus; extern const std::size_t num_frames_in_flight; @@ -28,8 +26,7 @@ extern double g_last_time; extern std::uint64_t g_frame_count; extern float g_fps; -extern std::shared_ptr g_draw_list_shared_data; -extern gui::DrawList g_draw_list; +// extern io::DeviceEventQueue g_device_event_queue; namespace { @@ -60,6 +57,8 @@ namespace ComPtr g_additional_picture_resource = nullptr; D3D12_GPU_DESCRIPTOR_HANDLE g_additional_picture_handle = {.ptr = 0}; + draw::DrawList g_draw_list; + [[nodiscard]] auto load_texture( const std::uint8_t* texture_data, const std::uint32_t texture_width, @@ -278,9 +277,19 @@ auto prometheus_init() -> void { print_time(); - using functional::operators::operator|; - g_draw_list.draw_list_flag(gui::DrawListFlag::ANTI_ALIASED_LINE | gui::DrawListFlag::ANTI_ALIASED_FILL); - g_draw_list.shared_data(g_draw_list_shared_data); + const auto glyph_range = i18n::RangeBuilder{}.simplified_chinese_common().range(); + const auto font_option = + draw::Font::option() + .path(R"(C:\Windows\Fonts\msyh.ttc)") + .glyph_ranges(i18n::RangeBuilder{}.simplified_chinese_common().range()) + .pixel_height(18); + + auto font = std::make_shared(); + auto font_texture = font->load(font_option); + + draw::Context::instance().set_default_font(font); + + g_draw_list.draw_list_flag(draw::DrawListFlag::ANTI_ALIASED_LINE | draw::DrawListFlag::ANTI_ALIASED_LINE_USE_TEXTURE | draw::DrawListFlag::ANTI_ALIASED_FILL); // Create the root signature { @@ -559,18 +568,10 @@ auto prometheus_init() -> void // Load default font texture { - const auto& default_font = g_draw_list_shared_data->get_default_font(); - - const auto font_data = default_font.texture_data.get(); - assert(font_data); - - const auto font_width = default_font.texture_size.width; - const auto font_height = default_font.texture_size.height; - - const auto load_font_texture_result = load_texture( - reinterpret_cast(font_data), - font_width, - font_height, + [[maybe_unused]] const auto load_font_texture_result = load_texture( + reinterpret_cast(font_texture.data().get()), + font_texture.width(), + font_texture.height(), g_shader_resource_view_descriptor_heap, 0, g_font_handle, @@ -578,7 +579,7 @@ auto prometheus_init() -> void ); assert(load_font_texture_result); - g_draw_list_shared_data->get_default_font().texture_id = static_cast(g_font_handle.ptr); + font_texture.bind(static_cast(g_font_handle.ptr)); } // Load additional picture texture @@ -589,7 +590,7 @@ auto prometheus_init() -> void auto* data = stbi_load(ASSETS_PATH_PIC, &image_width, &image_height, nullptr, 4); assert(data); - const auto load_additional_texture_result = load_texture( + [[maybe_unused]] const auto load_additional_texture_result = load_texture( data, image_width, image_height, @@ -614,7 +615,7 @@ auto prometheus_render() -> void { g_draw_list.text(24.f, {10, 10}, primitive::colors::blue, std::format("FPS: {:.3f}", g_fps)); - g_draw_list.text(24.f, {50, 50}, primitive::colors::red, "The quick brown fox jumps over the lazy dog.\nHello world!\n你好世界!\n"); + g_draw_list.text(18.f, {50, 50}, primitive::colors::red, "The quick brown fox jumps over the lazy dog.\nHello world!\n你好世界!\n"); g_draw_list.line({200, 100}, {200, 300}, primitive::colors::red); g_draw_list.line({100, 200}, {300, 200}, primitive::colors::red); @@ -681,13 +682,10 @@ auto prometheus_render() -> void g_draw_list.triangle_filled({800, 450}, {700, 750}, {850, 800}, primitive::colors::gold); - // font texture - g_draw_list.image(g_draw_list_shared_data->get_default_font().texture_id, {900, 20, 1200, 320}); - g_draw_list.image_rounded(static_cast(g_additional_picture_handle.ptr), {900, 350, 1200, 650}, 10); - - #if GAL_PROMETHEUS_GUI_DRAW_LIST_DEBUG - g_draw_list.bind_debug_info(); - #endif + // font + g_draw_list.image(draw::Context::instance().font().texture_id(), {900, 20, 300, 300}); + // image + g_draw_list.image_rounded(static_cast(g_additional_picture_handle.ptr), {900, 350, 300, 300}, 10); } auto prometheus_draw() -> void @@ -781,7 +779,7 @@ auto prometheus_draw() -> void std::ranges::transform( vertex_list, mapped_vertex, - [](const gui::DrawList::vertex_type& vertex) -> d3d_vertex_type + [](const draw::DrawList::vertex_type& vertex) -> d3d_vertex_type { // return { // .position = {vertex.position.x, vertex.position.y}, diff --git a/unit_test/src/gui/main_dx12.cpp b/unit_test/src/draw/dx12/main.cpp similarity index 96% rename from unit_test/src/gui/main_dx12.cpp rename to unit_test/src/draw/dx12/main.cpp index 3fb9d807..fe864e53 100644 --- a/unit_test/src/gui/main_dx12.cpp +++ b/unit_test/src/draw/dx12/main.cpp @@ -1,5 +1,5 @@ -#include "def.hpp" -#include "dx_error_handler.hpp" +#include "../win/def.hpp" +#include "../common/print_time.hpp" #include #include @@ -25,11 +25,7 @@ #pragma comment(lib, "dxguid.lib") #endif -namespace -{ - using namespace gal::prometheus; - using Microsoft::WRL::ComPtr; -} +using Microsoft::WRL::ComPtr; extern constexpr std::size_t num_frames_in_flight = 3; @@ -43,9 +39,6 @@ double g_last_time = 0; std::uint64_t g_frame_count = 0; float g_fps = 0; -auto g_draw_list_shared_data = std::make_shared(); -gui::DrawList g_draw_list; - extern auto glfw_callback_setup(GLFWwindow& w) -> void; extern auto prometheus_init() -> void; @@ -89,7 +82,7 @@ namespace auto glfw_error_callback(const int error, const char* description) -> void { - std::println("GLFW ERROR({}): {}", error, description); + std::println(stderr, "GLFW ERROR({}): {}", error, description); } auto create_device(GLFWwindow& window) -> bool; @@ -136,9 +129,6 @@ int main(int, char**) return 1; } - const auto range = gui::glyph_range_simplified_chinese_common(); - g_draw_list_shared_data->set_default_font(gui::load_font(R"(C:\Windows\Fonts\msyh.ttc)", 18, range)); - // Setup Platform/Renderer backends win32_init(*window); d3d_init(); @@ -282,7 +272,7 @@ namespace (void)info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, true); } - // todo: cannot run program normally + // todo // `g_device->CreateDescriptorHeap` failed // D3D12: Removing Device. // D3D12 MESSAGE: diff --git a/unit_test/src/gui/main_vulkan.cpp b/unit_test/src/draw/vulkan/main.cpp similarity index 100% rename from unit_test/src/gui/main_vulkan.cpp rename to unit_test/src/draw/vulkan/main.cpp diff --git a/unit_test/src/gui/dx_error_handler.hpp b/unit_test/src/draw/win/def.hpp similarity index 61% rename from unit_test/src/gui/dx_error_handler.hpp rename to unit_test/src/draw/win/def.hpp index 834c0b6d..0e64447c 100644 --- a/unit_test/src/gui/dx_error_handler.hpp +++ b/unit_test/src/draw/win/def.hpp @@ -1,11 +1,26 @@ #pragma once -#include - #include #include #include +#include + +#include + +struct d3d_vertex_type +{ + float position[2]; + float uv[2]; + std::uint32_t color; +}; + +using d3d_index_type = gal::prometheus::draw::DrawList::index_type; +using d3d_projection_matrix_type = float[4][4]; + +static_assert(sizeof(gal::prometheus::draw::DrawList::vertex_type) == sizeof(d3d_vertex_type)); +static_assert(sizeof(gal::prometheus::draw::DrawList::index_type) == sizeof(d3d_index_type)); + template auto check_hr_error( const HRESULT hr, diff --git a/unit_test/src/utility/aligned_union.cpp b/unit_test/src/functional/aligned_union.cpp similarity index 75% rename from unit_test/src/utility/aligned_union.cpp rename to unit_test/src/functional/aligned_union.cpp index 4d3975b0..d54494e7 100644 --- a/unit_test/src/utility/aligned_union.cpp +++ b/unit_test/src/functional/aligned_union.cpp @@ -1,19 +1,20 @@ -#include +#include +#include -import std; -import gal.prometheus.test; -import gal.prometheus.utility; +#include +// functional::aligned_union +#include + +using namespace gal::prometheus; namespace { - using namespace gal::prometheus; - using namespace utility; - - GAL_PROMETHEUS_NO_DESTROY test::suite<"utility.aligned_union"> _ = [] + GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"functional.aligned_union"> _ = [] { - using namespace test; + using namespace unit_test; + using namespace functional; - ignore_pass / "arithmethic"_test = [] + "arithmethic"_test = [] { using union_type = AlignedUnion; @@ -27,30 +28,30 @@ namespace expect(u.load() == 3.14_f) << fatal; }; - ignore_pass / "pointer"_test = [] + "pointer"_test = [] { using union_type = AlignedUnion; static_assert(union_type::max_size == sizeof(int*)); - int value_i = 42; + int value_i = 42; unsigned value_u = 123; - float value_f = 3.14f; + float value_f = 3.14f; auto* pointer_i = &value_i; auto* pointer_u = &value_u; auto* pointer_f = &value_f; union_type u{union_type::constructor_tag{}, pointer_i}; - expect(u.load() == as{pointer_i}) << fatal; + expect(u.load() == value(pointer_i)) << fatal; u.store(pointer_u); - expect(u.load() == as{pointer_u}) << fatal; + expect(u.load() == value(pointer_u)) << fatal; u.store(pointer_f); - expect(u.load() == as{pointer_f}) << fatal; + expect(u.load() == value(pointer_f)) << fatal; }; - ignore_pass / "structure"_test = [] + "structure"_test = [] { struct struct1 { diff --git a/unit_test/src/functional/function_ref.cpp b/unit_test/src/functional/function_ref.cpp new file mode 100644 index 00000000..1eb95b4c --- /dev/null +++ b/unit_test/src/functional/function_ref.cpp @@ -0,0 +1,116 @@ +#include +// functional::function_ref +#include + +using namespace gal::prometheus; + +namespace +{ + GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"functional.function_ref"> _ = [] + { + using namespace unit_test; + using namespace functional; + + "functor"_test = [] + { + struct functor + { + constexpr auto operator()(const int a, const int b) const noexcept -> int { return a + b; } + + constexpr auto operator()(int& a) const noexcept -> void { a = 42; } + }; + + functor f{}; + + const auto a = take + to + f; + expect(a(42, 1337) == value(42 + 1337)) << fatal; + + const auto b = take + to + f; + expect(b(42, 1337) == value(42 + 1337)) << fatal; + + int v = 1337; + expect(v == 1337_i) << fatal; + + const auto c = take + to + f; + c(v); + expect(v == 42_i) << fatal; + + v = 1337; + const auto d = take + to + f; + // note: call functor::operator()(const int a, const int b) + d(v, 123); + expect(v == 1337_i) << fatal; + }; + + "function pointer"_test = [] + { + const auto f = +[](const int a, const int b) noexcept -> int { return a + b; }; + + const auto a = to + take + f; + expect(a(42, 1337) == value(42 + 1337)) << fatal; + + const auto b = to + take + f; + expect(b(42, 1337) == value(42 + 1337)) << fatal; + + // compatible + const auto c = to + take + f; + c(42, 1337); + }; + + "lambda"_test = [] + { + { + const auto f = [](const int a, const int b) noexcept -> int { return a + b; }; + + const auto a = take + to + f; + expect(a(42, 1337) == value(42 + 1337)) << fatal; + + const auto b = take + to + f; + expect(b(42, 1337) == value(42 + 1337)) << fatal; + + // compatible + const auto c = take + to + f; + c(42, 1337); + } + { + int i = 42; + const auto f = [&i](const int a, const int b) noexcept -> int { return i + a + b; }; + + const auto a = take + to + f; + expect(a(42, 1337) == value(i + 42 + 1337)) << fatal; + + const auto b = take + to + f; + expect(b(42, 1337) == value(i + 42 + 1337)) << fatal; + + // compatible + const auto c = take + to + f; + c(42, 1337); + } + }; + + "member function"_test = [] + { + class Foo + { + int _{0}; + + public: + [[nodiscard]] constexpr auto bar(const int a, const int b) noexcept -> int // NOLINT + { + _ = a + b; + + return _; + } + }; + + Foo foo{}; + + const auto a = ref([](Foo& f, const int v1, const int v2) noexcept -> int { return f.bar(v1, v2); }); + expect(a(foo, 42, 1337) == value(42 + 1337)) << fatal; + + auto function_pointer = std::mem_fn(&Foo::bar); + const auto b = ref(function_pointer); + expect(b(foo, 42, 1337) == value(42 + 1337)) << fatal; + }; + }; +} diff --git a/unit_test/src/gui/def.hpp b/unit_test/src/gui/def.hpp deleted file mode 100644 index 5400fe94..00000000 --- a/unit_test/src/gui/def.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#if GAL_PROMETHEUS_USE_MODULE -import gal.prometheus; -#else -#include -#endif - -#include -#include -#include -#include - -struct d3d_vertex_type -{ - float position[2]; - float uv[2]; - std::uint32_t color; -}; - -using d3d_index_type = gal::prometheus::gui::DrawList::index_type; -using d3d_projection_matrix_type = float[4][4]; - -static_assert(sizeof(gal::prometheus::gui::DrawList::vertex_type) == sizeof(d3d_vertex_type)); -static_assert(sizeof(gal::prometheus::gui::DrawList::index_type) == sizeof(d3d_index_type)); - -inline auto print_time(const std::source_location& location = std::source_location::current()) noexcept -> void -{ - std::println(stdout, "[{:%r}] {}", std::chrono::floor(std::chrono::system_clock::now()), location.function_name()); -} diff --git a/unit_test/src/main.cpp b/unit_test/src/main.cpp index 76e81970..9efc2605 100644 --- a/unit_test/src/main.cpp +++ b/unit_test/src/main.cpp @@ -1 +1,136 @@ -int main() { return 0; } +#include +#include + +using namespace gal::prometheus; + +int main(const int, char*[]) +{ + using parser_type = clp::CommandLineOptionParser<>; + using option_type = parser_type::option_type; + + clp::CommandLineOptionParser parser{}; + + parser + .add_option("tab-width", option_type::implicit_value("4")) + .add_option("max-failures", option_type::implicit_value("100")) + .add_option("dry,dry-run", option_type::default_value("true")) + .add_option("exec-suite-name") + .add_option("exec-test-name") + .add_option("call-debugger-if-fail", option_type::default_value("false")) + .add_option("call-debugger-if-fatal", option_type::default_value("true")) + // + .add_alias("x-fail", "call-debugger-if-fail") + .add_alias("x-fatal", "call-debugger-if-fatal") + // + ; + + unit_test::config_type config{}; + + try + { + parser.parse(); + } + catch (const platform::IException& exception) + { + exception.print(); + config.dry_run = true; + return 1; + } + + const auto tab_width = parser["tab-width"].as(); + const auto max_failures = parser["max-failures"].as(); + const auto dry_run = parser["dry-run"].as(); + const auto call_debugger_if_fail = parser["call-debugger-if-fail"].as(); + const auto call_debugger_if_fatal = parser["call-debugger-if-fatal"].as(); + + if (tab_width.has_value()) + { + config.tab_width = *tab_width; + } + if (max_failures.has_value()) + { + config.abort_after_n_failures = *max_failures; + } + if (dry_run.has_value()) + { + config.dry_run = *dry_run; + } + if (call_debugger_if_fail.has_value()) + { + if (*call_debugger_if_fail) + { + config.break_point_level |= unit_test::config_type::BreakPointLevel::FAILURE; + } + else + { + config.break_point_level &= ~unit_test::config_type::BreakPointLevel::FAILURE; + } + } + if (call_debugger_if_fatal.has_value()) + { + if (*call_debugger_if_fatal) + { + config.break_point_level |= unit_test::config_type::BreakPointLevel::FATAL; + } + else + { + config.break_point_level &= ~unit_test::config_type::BreakPointLevel::FATAL; + } + } + + if (const auto& option = parser["exec-suite-name"]; + option.set()) + { + if (const auto wildcard = option.as(); + wildcard.has_value() and *wildcard == "*") + { + config.filter_suite = [](const unit_test::suite_node_type& node) noexcept -> bool + { + std::ignore = node; + return true; + }; + } + else if (auto suite_names = option.as>(); + suite_names.has_value()) + { + config.filter_suite = + [suite = std::move(*suite_names)](const unit_test::suite_node_type& node) noexcept -> bool + { + return std::ranges::contains(suite, node.name); + }; + } + } + + if (const auto& option = parser["exec-test-name"]; + option.set()) + { + if (const auto wildcard = option.as(); + wildcard.has_value() and *wildcard == "*") + { + config.filter_test = [](const unit_test::test_node_type& node) noexcept -> bool + { + std::ignore = node; + return true; + }; + } + else if (auto test_names = option.as>(); + test_names.has_value()) + { + config.filter_test = + [test = std::move(*test_names)](const unit_test::test_node_type& node) noexcept -> bool + { + if (node.parent != nullptr) + { + // child + return true; + } + + return std::ranges::contains(test, node.name) and not std::ranges::contains(node.categories.get(), "skip"); + }; + } + } + + set_config(std::move(config)); + + return 0; +} diff --git a/unit_test/src/meta/dimension.cpp b/unit_test/src/meta/dimension.cpp new file mode 100644 index 00000000..be76fc2e --- /dev/null +++ b/unit_test/src/meta/dimension.cpp @@ -0,0 +1,1720 @@ +// meta::dimension +#include + +using namespace gal::prometheus; + +namespace +{ + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_PUSH + // warning: missing initializer for member ‘{anonymous}::Type::’ [-Wmissing-field-initializers] + // constexpr Type value{.x = 42, .y = 1337}; + GAL_PROMETHEUS_COMPILER_DISABLE_GNU_WARNING(-Wmissing-field-initializers) + + namespace from + { + struct point_float + { + float x; + float y; + }; + + struct point_int : meta::dimension + { + int x; + int y; + }; + + static_assert(sizeof(point_float) == sizeof(float) * 2); + static_assert(sizeof(point_float) == sizeof(point_int)); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + + constexpr point_float pf_1{.x = 42.12345f, .y = 1337.12345f}; + + constexpr point_int pi_1{.x = 42, .y = 1337}; + constexpr point_int pi_2{.x = 42, .y = 42}; + + static_assert(std::ranges::all_of(point_int::from(pf_1) == pi_1, std::identity{})); + static_assert(std::ranges::all_of(point_int::from(pf_1.x) == pi_2, std::identity{})); + static_assert(std::ranges::all_of(point_int::from(pi_1.x) == pi_2, std::identity{})); + } + + namespace to + { + struct point_float : meta::dimension + { + float x; + float y; + }; + + struct point_int + { + int x; + int y; + + [[nodiscard]] constexpr auto operator==(const point_int& other) const noexcept -> bool + { + return x == other.x and y == other.y; + } + }; + + static_assert(sizeof(point_float) == sizeof(float) * 2); + static_assert(sizeof(point_float) == sizeof(point_int)); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + + constexpr point_float pf_1{.x = 42.12345f, .y = 1337.12345f}; + constexpr point_int pi_1{.x = 42, .y = 1337}; + + constexpr auto t1 = pf_1.to(); + static_assert(t1.x == pf_1.x); // NOLINT(clang-diagnostic-float-equal) + static_assert(t1.y == pf_1.y); // NOLINT(clang-diagnostic-float-equal) + + constexpr auto t2 = pf_1.to(); + static_assert(t2.x == pi_1.x); + static_assert(t2.y == pi_1.y); + + constexpr auto to_int_1 = [](const auto value) noexcept -> int + { + return static_cast(value) + 123; + }; + + constexpr auto t3 = pf_1.to(to_int_1); + static_assert(t3.x == pi_1.x + 123); + static_assert(t3.y == pi_1.y + 123); + + constexpr auto to_int_2 = [](const auto value) noexcept -> int + { + return static_cast(value) + 123 * Index; + }; + + constexpr auto t4 = pf_1.to(to_int_2); + static_assert(t4.x == pi_1.x + 123 * 0); + static_assert(t4.y == pi_1.y + 123 * 1); + + struct to_int_3 + { + template + [[nodiscard]] constexpr auto operator()(const auto value) const noexcept -> decltype(auto) + { + if constexpr (Index == 0) + { + return to_int_1(value); + } + else + { + return to_int_2.operator()(value); + } + } + }; + + constexpr auto t5 = pf_1.to(to_int_3{}); + static_assert(t5.x == pi_1.x + 123); + static_assert(t5.y == pi_1.y + 123 * 1); + } + + namespace addition + { + struct wrapper_x + { + int value; + + // point_x == point ==> point_x.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + [[nodiscard]] constexpr auto operator+(const wrapper_x& other) const noexcept -> wrapper_x + { + return {.value = value + other.value}; + } + + [[nodiscard]] constexpr auto operator+(const int& other) const noexcept -> wrapper_x + { + return {.value = value + other}; + } + }; + + struct wrapper_x_equal + { + int value; + + // point_x_equal == point ==> point_x_equal.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + constexpr auto operator+=(const wrapper_x_equal& other) noexcept -> wrapper_x_equal& + { + value += other.value; + return *this; + } + + constexpr auto operator+=(const int& other) noexcept -> wrapper_x_equal& + { + value += other; + return *this; + } + }; + + struct point + { + int x; + int y; + }; + + struct point_x : meta::dimension + { + wrapper_x x; + wrapper_x y; + }; + + struct point_x_equal : meta::dimension + { + wrapper_x_equal x; + wrapper_x_equal y; + }; + + static_assert(sizeof(point) == sizeof(int) * 2); + static_assert(sizeof(point) == sizeof(point_x)); + static_assert(sizeof(point) == sizeof(point_x_equal)); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + + template + constexpr auto can_x = requires { std::declval() = std::declval() + std::declval(); }; + static_assert(can_x); + static_assert(not can_x); + + template + constexpr auto can_x_equal = requires { std::declval() += std::declval(); }; + static_assert(not can_x_equal); + static_assert(can_x_equal); + + constexpr point p{.x = 42, .y = 1337}; + + constexpr point_x px1{.x = {.value = 1234}, .y = {.value = 6789}}; + constexpr point_x px2{.x = {.value = 6789}, .y = {.value = 1234}}; + + constexpr point_x_equal pxe1{.x = {.value = 6789}, .y = {.value = 1234}}; + constexpr point_x_equal pxe2{.x = {.value = 1234}, .y = {.value = 6789}}; + + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs + rhs; + } + + template + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs.template add(Index)>(rhs); + } + + [[nodiscard]] constexpr auto do_x_equal(const auto& lhs, const auto& rhs) noexcept -> auto + { + auto t = lhs.copy(); + t += rhs; + return t; + } + + template + [[nodiscard]] constexpr auto do_x_equal(const auto& lhs, const auto& rhs) noexcept -> auto + { + auto t = lhs.copy(); + t.template add_equal(Index)>(rhs); + return t; + } + + // point_x + point_x + static_assert(std::ranges::all_of(do_x(px1, px2) == point{.x = px1.x.value + px2.x.value, .y = px1.y.value + px2.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, px2) == point{.x = px1.x.value + px2.x.value, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, px2) == point{.x = px1.x.value, .y = px1.y.value + px2.y.value}, std::identity{})); + // point_x + point + static_assert(std::ranges::all_of(do_x(px1, p) == point{.x = px1.x.value + p.x, .y = px1.y.value + p.y}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, p) == point{.x = px1.x.value + p.x, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, p) == point{.x = px1.x.value, .y = px1.y.value + p.y}, std::identity{})); + // point_x + value + static_assert(std::ranges::all_of(do_x(px1, 13579) == point{.x = px1.x.value + 13579, .y = px1.y.value + 13579}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, 13579) == point{.x = px1.x.value + 13579, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, 13579) == point{.x = px1.x.value, .y = px1.y.value + 13579}, std::identity{})); + // point_x_equal += point_x_equal + static_assert(std::ranges::all_of(do_x_equal(pxe1, pxe2) == point{.x = pxe1.x.value + pxe2.x.value, .y = pxe1.y.value + pxe2.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, pxe2) == point{.x = pxe1.x.value + pxe2.x.value, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, pxe2) == point{.x = pxe1.x.value, .y = pxe1.y.value + pxe2.y.value}, std::identity{})); + // point_x_equal += point + static_assert(std::ranges::all_of(do_x_equal(pxe1, p) == point{.x = pxe1.x.value + p.x, .y = pxe1.y.value + p.y}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, p) == point{.x = pxe1.x.value + p.x, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, p) == point{.x = pxe1.x.value, .y = pxe1.y.value + p.y}, std::identity{})); + // point_x_equal += value + static_assert(std::ranges::all_of(do_x_equal(pxe1, 13579) == point{.x = pxe1.x.value + 13579, .y = pxe1.y.value + 13579}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, 13579) == point{.x = pxe1.x.value + 13579, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, 13579) == point{.x = pxe1.x.value, .y = pxe1.y.value + 13579}, std::identity{})); + } + + namespace subtraction + { + struct wrapper_x + { + int value; + + // point_x == point ==> point_x.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + [[nodiscard]] constexpr auto operator-(const wrapper_x& other) const noexcept -> wrapper_x + { + return {.value = value - other.value}; + } + + [[nodiscard]] constexpr auto operator-(const int& other) const noexcept -> wrapper_x + { + return {.value = value - other}; + } + }; + + struct wrapper_x_equal + { + int value; + + // point_x_equal == point ==> point_x_equal.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + constexpr auto operator-=(const wrapper_x_equal& other) noexcept -> wrapper_x_equal& + { + value -= other.value; + return *this; + } + + constexpr auto operator-=(const int& other) noexcept -> wrapper_x_equal& + { + value -= other; + return *this; + } + }; + + struct point + { + int x; + int y; + }; + + struct point_x : meta::dimension + { + wrapper_x x; + wrapper_x y; + }; + + struct point_x_equal : meta::dimension + { + wrapper_x_equal x; + wrapper_x_equal y; + }; + + static_assert(sizeof(point) == sizeof(int) * 2); + static_assert(sizeof(point) == sizeof(point_x)); + static_assert(sizeof(point) == sizeof(point_x_equal)); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + + template + constexpr auto can_x = requires { std::declval() = std::declval() - std::declval(); }; + static_assert(can_x); + static_assert(not can_x); + + template + constexpr auto can_x_equal = requires { std::declval() -= std::declval(); }; + static_assert(not can_x_equal); + static_assert(can_x_equal); + + constexpr point p{.x = 42, .y = 1337}; + + constexpr point_x px1{.x = {.value = 1234}, .y = {.value = 6789}}; + constexpr point_x px2{.x = {.value = 6789}, .y = {.value = 1234}}; + + constexpr point_x_equal pxe1{.x = {.value = 6789}, .y = {.value = 1234}}; + constexpr point_x_equal pxe2{.x = {.value = 1234}, .y = {.value = 6789}}; + + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs - rhs; + } + + template + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs.template subtract(Index)>(rhs); + } + + [[nodiscard]] constexpr auto do_x_equal(const auto& lhs, const auto& rhs) noexcept -> auto + { + auto t = lhs.copy(); + t -= rhs; + return t; + } + + template + [[nodiscard]] constexpr auto do_x_equal(const auto& lhs, const auto& rhs) noexcept -> auto + { + auto t = lhs.copy(); + t.template subtract_equal(Index)>(rhs); + return t; + } + + // point_x - point_x + static_assert(std::ranges::all_of(do_x(px1, px2) == point{.x = px1.x.value - px2.x.value, .y = px1.y.value - px2.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, px2) == point{.x = px1.x.value - px2.x.value, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, px2) == point{.x = px1.x.value, .y = px1.y.value - px2.y.value}, std::identity{})); + // point_x - point + static_assert(std::ranges::all_of(do_x(px1, p) == point{.x = px1.x.value - p.x, .y = px1.y.value - p.y}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, p) == point{.x = px1.x.value - p.x, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, p) == point{.x = px1.x.value, .y = px1.y.value - p.y}, std::identity{})); + // point_x - value + static_assert(std::ranges::all_of(do_x(px1, 13579) == point{.x = px1.x.value - 13579, .y = px1.y.value - 13579}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, 13579) == point{.x = px1.x.value - 13579, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, 13579) == point{.x = px1.x.value, .y = px1.y.value - 13579}, std::identity{})); + // point_x_equal -= point_x_equal + static_assert(std::ranges::all_of(do_x_equal(pxe1, pxe2) == point{.x = pxe1.x.value - pxe2.x.value, .y = pxe1.y.value - pxe2.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, pxe2) == point{.x = pxe1.x.value - pxe2.x.value, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, pxe2) == point{.x = pxe1.x.value, .y = pxe1.y.value - pxe2.y.value}, std::identity{})); + // point_x_equal -= point + static_assert(std::ranges::all_of(do_x_equal(pxe1, p) == point{.x = pxe1.x.value - p.x, .y = pxe1.y.value - p.y}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, p) == point{.x = pxe1.x.value - p.x, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, p) == point{.x = pxe1.x.value, .y = pxe1.y.value - p.y}, std::identity{})); + // point_x_equal -= value + static_assert(std::ranges::all_of(do_x_equal(pxe1, 13579) == point{.x = pxe1.x.value - 13579, .y = pxe1.y.value - 13579}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, 13579) == point{.x = pxe1.x.value - 13579, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, 13579) == point{.x = pxe1.x.value, .y = pxe1.y.value - 13579}, std::identity{})); + } + + namespace multiplication + { + struct wrapper_x + { + int value; + + // point_x == point ==> point_x.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + [[nodiscard]] constexpr auto operator*(const wrapper_x& other) const noexcept -> wrapper_x + { + return {.value = value * other.value}; + } + + [[nodiscard]] constexpr auto operator*(const int& other) const noexcept -> wrapper_x + { + return {.value = value * other}; + } + }; + + struct wrapper_x_equal + { + int value; + + // point_x_equal == point ==> point_x_equal.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + constexpr auto operator*=(const wrapper_x_equal& other) noexcept -> wrapper_x_equal& + { + value *= other.value; + return *this; + } + + constexpr auto operator*=(const int& other) noexcept -> wrapper_x_equal& + { + value *= other; + return *this; + } + }; + + struct point + { + int x; + int y; + }; + + struct point_x : meta::dimension + { + wrapper_x x; + wrapper_x y; + }; + + struct point_x_equal : meta::dimension + { + wrapper_x_equal x; + wrapper_x_equal y; + }; + + static_assert(sizeof(point) == sizeof(int) * 2); + static_assert(sizeof(point) == sizeof(point_x)); + static_assert(sizeof(point) == sizeof(point_x_equal)); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + + template + constexpr auto can_x = requires { std::declval() = std::declval() * std::declval(); }; + static_assert(can_x); + static_assert(not can_x); + + template + constexpr auto can_x_equal = requires { std::declval() *= std::declval(); }; + static_assert(not can_x_equal); + static_assert(can_x_equal); + + constexpr point p{.x = 42, .y = 1337}; + + constexpr point_x px1{.x = {.value = 1234}, .y = {.value = 6789}}; + constexpr point_x px2{.x = {.value = 6789}, .y = {.value = 1234}}; + + constexpr point_x_equal pxe1{.x = {.value = 6789}, .y = {.value = 1234}}; + constexpr point_x_equal pxe2{.x = {.value = 1234}, .y = {.value = 6789}}; + + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs * rhs; + } + + template + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs.template multiply(Index)>(rhs); + } + + [[nodiscard]] constexpr auto do_x_equal(const auto& lhs, const auto& rhs) noexcept -> auto + { + auto t = lhs.copy(); + t *= rhs; + return t; + } + + template + [[nodiscard]] constexpr auto do_x_equal(const auto& lhs, const auto& rhs) noexcept -> auto + { + auto t = lhs.copy(); + t.template multiply_equal(Index)>(rhs); + return t; + } + + // point_x * point_x + static_assert(std::ranges::all_of(do_x(px1, px2) == point{.x = px1.x.value * px2.x.value, .y = px1.y.value * px2.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, px2) == point{.x = px1.x.value * px2.x.value, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, px2) == point{.x = px1.x.value, .y = px1.y.value * px2.y.value}, std::identity{})); + // point_x * point + static_assert(std::ranges::all_of(do_x(px1, p) == point{.x = px1.x.value * p.x, .y = px1.y.value * p.y}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, p) == point{.x = px1.x.value * p.x, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, p) == point{.x = px1.x.value, .y = px1.y.value * p.y}, std::identity{})); + // point_x * value + static_assert(std::ranges::all_of(do_x(px1, 13579) == point{.x = px1.x.value * 13579, .y = px1.y.value * 13579}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, 13579) == point{.x = px1.x.value * 13579, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, 13579) == point{.x = px1.x.value, .y = px1.y.value * 13579}, std::identity{})); + // point_x_equal *= point_x_equal + static_assert(std::ranges::all_of(do_x_equal(pxe1, pxe2) == point{.x = pxe1.x.value * pxe2.x.value, .y = pxe1.y.value * pxe2.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, pxe2) == point{.x = pxe1.x.value * pxe2.x.value, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, pxe2) == point{.x = pxe1.x.value, .y = pxe1.y.value * pxe2.y.value}, std::identity{})); + // point_x_equal *= point + static_assert(std::ranges::all_of(do_x_equal(pxe1, p) == point{.x = pxe1.x.value * p.x, .y = pxe1.y.value * p.y}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, p) == point{.x = pxe1.x.value * p.x, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, p) == point{.x = pxe1.x.value, .y = pxe1.y.value * p.y}, std::identity{})); + // point_x_equal *= value + static_assert(std::ranges::all_of(do_x_equal(pxe1, 13579) == point{.x = pxe1.x.value * 13579, .y = pxe1.y.value * 13579}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, 13579) == point{.x = pxe1.x.value * 13579, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, 13579) == point{.x = pxe1.x.value, .y = pxe1.y.value * 13579}, std::identity{})); + } + + namespace division + { + struct wrapper_x + { + float value; + + // point_x == point ==> point_x.members == point.members ==> x == float + [[nodiscard]] constexpr auto operator==(const float other) const noexcept -> bool + { + return value == other; // NOLINT(clang-diagnostic-float-equal) + } + + [[nodiscard]] constexpr auto operator/(const wrapper_x& other) const noexcept -> wrapper_x + { + return {.value = value / other.value}; + } + + [[nodiscard]] constexpr auto operator/(const float& other) const noexcept -> wrapper_x + { + return {.value = value / other}; + } + }; + + struct wrapper_x_equal + { + float value; + + // point_x_equal == point ==> point_x_equal.members == point.members ==> x == float + [[nodiscard]] constexpr auto operator==(const float other) const noexcept -> bool + { + return value == other; // NOLINT(clang-diagnostic-float-equal) + } + + constexpr auto operator/=(const wrapper_x_equal& other) noexcept -> wrapper_x_equal& + { + value /= other.value; + return *this; + } + + constexpr auto operator/=(const float& other) noexcept -> wrapper_x_equal& + { + value /= other; + return *this; + } + }; + + struct point + { + float x; + float y; + }; + + struct point_x : meta::dimension + { + wrapper_x x; + wrapper_x y; + }; + + struct point_x_equal : meta::dimension + { + wrapper_x_equal x; + wrapper_x_equal y; + }; + + static_assert(sizeof(point) == sizeof(float) * 2); + static_assert(sizeof(point) == sizeof(point_x)); + static_assert(sizeof(point) == sizeof(point_x_equal)); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + + template + constexpr auto can_x = requires { std::declval() = std::declval() / std::declval(); }; + static_assert(can_x); + static_assert(not can_x); + + template + constexpr auto can_x_equal = requires { std::declval() /= std::declval(); }; + static_assert(not can_x_equal); + static_assert(can_x_equal); + + constexpr point p{.x = 42, .y = 1337}; + + constexpr point_x px1{.x = {.value = 1234}, .y = {.value = 6789}}; + constexpr point_x px2{.x = {.value = 6789}, .y = {.value = 1234}}; + + constexpr point_x_equal pxe1{.x = {.value = 6789}, .y = {.value = 1234}}; + constexpr point_x_equal pxe2{.x = {.value = 1234}, .y = {.value = 6789}}; + + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs / rhs; + } + + template + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs.template divide(Index)>(rhs); + } + + [[nodiscard]] constexpr auto do_x_equal(const auto& lhs, const auto& rhs) noexcept -> auto + { + auto t = lhs.copy(); + t /= rhs; + return t; + } + + template + [[nodiscard]] constexpr auto do_x_equal(const auto& lhs, const auto& rhs) noexcept -> auto + { + auto t = lhs.copy(); + t.template divide_equal(Index)>(rhs); + return t; + } + + // point_x / point_x + static_assert(std::ranges::all_of(do_x(px1, px2) == point{.x = px1.x.value / px2.x.value, .y = px1.y.value / px2.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, px2) == point{.x = px1.x.value / px2.x.value, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, px2) == point{.x = px1.x.value, .y = px1.y.value / px2.y.value}, std::identity{})); + // point_x / point + static_assert(std::ranges::all_of(do_x(px1, p) == point{.x = px1.x.value / p.x, .y = px1.y.value / p.y}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, p) == point{.x = px1.x.value / p.x, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, p) == point{.x = px1.x.value, .y = px1.y.value / p.y}, std::identity{})); + // point_x / value + static_assert(std::ranges::all_of(do_x(px1, 13579) == point{.x = px1.x.value / 13579, .y = px1.y.value / 13579}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, 13579) == point{.x = px1.x.value / 13579, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, 13579) == point{.x = px1.x.value, .y = px1.y.value / 13579}, std::identity{})); + // point_x_equal /= point_x_equal + static_assert(std::ranges::all_of(do_x_equal(pxe1, pxe2) == point{.x = pxe1.x.value / pxe2.x.value, .y = pxe1.y.value / pxe2.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, pxe2) == point{.x = pxe1.x.value / pxe2.x.value, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, pxe2) == point{.x = pxe1.x.value, .y = pxe1.y.value / pxe2.y.value}, std::identity{})); + // point_x_equal /= point + static_assert(std::ranges::all_of(do_x_equal(pxe1, p) == point{.x = pxe1.x.value / p.x, .y = pxe1.y.value / p.y}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, p) == point{.x = pxe1.x.value / p.x, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, p) == point{.x = pxe1.x.value, .y = pxe1.y.value / p.y}, std::identity{})); + // point_x_equal /= value + static_assert(std::ranges::all_of(do_x_equal(pxe1, 13579) == point{.x = pxe1.x.value / 13579, .y = pxe1.y.value / 13579}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, 13579) == point{.x = pxe1.x.value / 13579, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, 13579) == point{.x = pxe1.x.value, .y = pxe1.y.value / 13579}, std::identity{})); + } + + namespace modulus + { + struct wrapper_x + { + int value; + + // point_x == point ==> point_x.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + [[nodiscard]] constexpr auto operator%(const wrapper_x& other) const noexcept -> wrapper_x + { + return {.value = value % other.value}; + } + + [[nodiscard]] constexpr auto operator%(const int& other) const noexcept -> wrapper_x + { + return {.value = value % other}; + } + }; + + struct wrapper_x_equal + { + int value; + + // point_x_equal == point ==> point_x_equal.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + constexpr auto operator%=(const wrapper_x_equal& other) noexcept -> wrapper_x_equal& + { + value %= other.value; + return *this; + } + + constexpr auto operator%=(const int& other) noexcept -> wrapper_x_equal& + { + value %= other; + return *this; + } + }; + + struct point + { + int x; + int y; + }; + + struct point_x : meta::dimension + { + wrapper_x x; + wrapper_x y; + }; + + struct point_x_equal : meta::dimension + { + wrapper_x_equal x; + wrapper_x_equal y; + }; + + static_assert(sizeof(point) == sizeof(int) * 2); + static_assert(sizeof(point) == sizeof(point_x)); + static_assert(sizeof(point) == sizeof(point_x_equal)); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + + template + constexpr auto can_x = requires { std::declval() = std::declval() % std::declval(); }; + static_assert(can_x); + static_assert(not can_x); + + template + constexpr auto can_x_equal = requires { std::declval() %= std::declval(); }; + static_assert(not can_x_equal); + static_assert(can_x_equal); + + constexpr point p{.x = 42, .y = 1337}; + + constexpr point_x px1{.x = {.value = 1234}, .y = {.value = 6789}}; + constexpr point_x px2{.x = {.value = 6789}, .y = {.value = 1234}}; + + constexpr point_x_equal pxe1{.x = {.value = 6789}, .y = {.value = 1234}}; + constexpr point_x_equal pxe2{.x = {.value = 1234}, .y = {.value = 6789}}; + + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs % rhs; + } + + template + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs.template mod(Index)>(rhs); + } + + [[nodiscard]] constexpr auto do_x_equal(const auto& lhs, const auto& rhs) noexcept -> auto + { + auto t = lhs.copy(); + t %= rhs; + return t; + } + + template + [[nodiscard]] constexpr auto do_x_equal(const auto& lhs, const auto& rhs) noexcept -> auto + { + auto t = lhs.copy(); + t.template mod_equal(Index)>(rhs); + return t; + } + + // point_x % point_x + static_assert(std::ranges::all_of(do_x(px1, px2) == point{.x = px1.x.value % px2.x.value, .y = px1.y.value % px2.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, px2) == point{.x = px1.x.value % px2.x.value, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, px2) == point{.x = px1.x.value, .y = px1.y.value % px2.y.value}, std::identity{})); + // point_x % point + static_assert(std::ranges::all_of(do_x(px1, p) == point{.x = px1.x.value % p.x, .y = px1.y.value % p.y}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, p) == point{.x = px1.x.value % p.x, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, p) == point{.x = px1.x.value, .y = px1.y.value % p.y}, std::identity{})); + // point_x % value + static_assert(std::ranges::all_of(do_x(px1, 13579) == point{.x = px1.x.value % 13579, .y = px1.y.value % 13579}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, 13579) == point{.x = px1.x.value % 13579, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, 13579) == point{.x = px1.x.value, .y = px1.y.value % 13579}, std::identity{})); + // point_x_equal %= point_x_equal + static_assert(std::ranges::all_of(do_x_equal(pxe1, pxe2) == point{.x = pxe1.x.value % pxe2.x.value, .y = pxe1.y.value % pxe2.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, pxe2) == point{.x = pxe1.x.value % pxe2.x.value, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, pxe2) == point{.x = pxe1.x.value, .y = pxe1.y.value % pxe2.y.value}, std::identity{})); + // point_x_equal %= point + static_assert(std::ranges::all_of(do_x_equal(pxe1, p) == point{.x = pxe1.x.value % p.x, .y = pxe1.y.value % p.y}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, p) == point{.x = pxe1.x.value % p.x, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, p) == point{.x = pxe1.x.value, .y = pxe1.y.value % p.y}, std::identity{})); + // point_x_equal %= value + static_assert(std::ranges::all_of(do_x_equal(pxe1, 13579) == point{.x = pxe1.x.value % 13579, .y = pxe1.y.value % 13579}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, 13579) == point{.x = pxe1.x.value % 13579, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, 13579) == point{.x = pxe1.x.value, .y = pxe1.y.value % 13579}, std::identity{})); + } + + namespace bit_and + { + struct wrapper_x + { + int value; + + // point_x == point ==> point_x.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + [[nodiscard]] constexpr auto operator&(const wrapper_x& other) const noexcept -> wrapper_x + { + return {.value = value & other.value}; + } + + [[nodiscard]] constexpr auto operator&(const int& other) const noexcept -> wrapper_x + { + return {.value = value & other}; + } + }; + + struct wrapper_x_equal + { + int value; + + // point_x_equal == point ==> point_x_equal.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + constexpr auto operator&=(const wrapper_x_equal& other) noexcept -> wrapper_x_equal& + { + value &= other.value; + return *this; + } + + constexpr auto operator&=(const int& other) noexcept -> wrapper_x_equal& + { + value &= other; + return *this; + } + }; + + struct point + { + int x; + int y; + }; + + struct point_x : meta::dimension + { + wrapper_x x; + wrapper_x y; + }; + + struct point_x_equal : meta::dimension + { + wrapper_x_equal x; + wrapper_x_equal y; + }; + + static_assert(sizeof(point) == sizeof(int) * 2); + static_assert(sizeof(point) == sizeof(point_x)); + static_assert(sizeof(point) == sizeof(point_x_equal)); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + + template + constexpr auto can_x = requires { std::declval() = std::declval() & std::declval(); }; + static_assert(can_x); + static_assert(not can_x); + + template + constexpr auto can_x_equal = requires { std::declval() &= std::declval(); }; + static_assert(not can_x_equal); + static_assert(can_x_equal); + + constexpr point p{.x = 42, .y = 1337}; + + constexpr point_x px1{.x = {.value = 1234}, .y = {.value = 6789}}; + constexpr point_x px2{.x = {.value = 6789}, .y = {.value = 1234}}; + + constexpr point_x_equal pxe1{.x = {.value = 6789}, .y = {.value = 1234}}; + constexpr point_x_equal pxe2{.x = {.value = 1234}, .y = {.value = 6789}}; + + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs & rhs; + } + + template + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs.template bit_and(Index)>(rhs); + } + + [[nodiscard]] constexpr auto do_x_equal(const auto& lhs, const auto& rhs) noexcept -> auto + { + auto t = lhs.copy(); + t &= rhs; + return t; + } + + template + [[nodiscard]] constexpr auto do_x_equal(const auto& lhs, const auto& rhs) noexcept -> auto + { + auto t = lhs.copy(); + t.template bit_and_equal(Index)>(rhs); + return t; + } + + // point_x & point_x + static_assert(std::ranges::all_of(do_x(px1, px2) == point{.x = px1.x.value & px2.x.value, .y = px1.y.value & px2.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, px2) == point{.x = px1.x.value & px2.x.value, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, px2) == point{.x = px1.x.value, .y = px1.y.value & px2.y.value}, std::identity{})); + // point_x & point + static_assert(std::ranges::all_of(do_x(px1, p) == point{.x = px1.x.value & p.x, .y = px1.y.value & p.y}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, p) == point{.x = px1.x.value & p.x, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, p) == point{.x = px1.x.value, .y = px1.y.value & p.y}, std::identity{})); + // point_x & value + static_assert(std::ranges::all_of(do_x(px1, 13579) == point{.x = px1.x.value & 13579, .y = px1.y.value & 13579}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, 13579) == point{.x = px1.x.value & 13579, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, 13579) == point{.x = px1.x.value, .y = px1.y.value & 13579}, std::identity{})); + // point_x_equal &= point_x_equal + static_assert(std::ranges::all_of(do_x_equal(pxe1, pxe2) == point{.x = pxe1.x.value & pxe2.x.value, .y = pxe1.y.value & pxe2.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, pxe2) == point{.x = pxe1.x.value & pxe2.x.value, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, pxe2) == point{.x = pxe1.x.value, .y = pxe1.y.value & pxe2.y.value}, std::identity{})); + // point_x_equal &= point + static_assert(std::ranges::all_of(do_x_equal(pxe1, p) == point{.x = pxe1.x.value & p.x, .y = pxe1.y.value & p.y}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, p) == point{.x = pxe1.x.value & p.x, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, p) == point{.x = pxe1.x.value, .y = pxe1.y.value & p.y}, std::identity{})); + // point_x_equal &= value + static_assert(std::ranges::all_of(do_x_equal(pxe1, 13579) == point{.x = pxe1.x.value & 13579, .y = pxe1.y.value & 13579}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, 13579) == point{.x = pxe1.x.value & 13579, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, 13579) == point{.x = pxe1.x.value, .y = pxe1.y.value & 13579}, std::identity{})); + } + + namespace bit_or + { + struct wrapper_x + { + int value; + + // point_x == point ==> point_x.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + [[nodiscard]] constexpr auto operator|(const wrapper_x& other) const noexcept -> wrapper_x + { + return {.value = value | other.value}; + } + + [[nodiscard]] constexpr auto operator|(const int& other) const noexcept -> wrapper_x + { + return {.value = value | other}; + } + }; + + struct wrapper_x_equal + { + int value; + + // point_x_equal == point ==> point_x_equal.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + constexpr auto operator|=(const wrapper_x_equal& other) noexcept -> wrapper_x_equal& + { + value |= other.value; + return *this; + } + + constexpr auto operator|=(const int& other) noexcept -> wrapper_x_equal& + { + value |= other; + return *this; + } + }; + + struct point + { + int x; + int y; + }; + + struct point_x : meta::dimension + { + wrapper_x x; + wrapper_x y; + }; + + struct point_x_equal : meta::dimension + { + wrapper_x_equal x; + wrapper_x_equal y; + }; + + static_assert(sizeof(point) == sizeof(int) * 2); + static_assert(sizeof(point) == sizeof(point_x)); + static_assert(sizeof(point) == sizeof(point_x_equal)); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + + template + constexpr auto can_x = requires { std::declval() = std::declval() | std::declval(); }; + static_assert(can_x); + static_assert(not can_x); + + template + constexpr auto can_x_equal = requires { std::declval() |= std::declval(); }; + static_assert(not can_x_equal); + static_assert(can_x_equal); + + constexpr point p{.x = 42, .y = 1337}; + + constexpr point_x px1{.x = {.value = 1234}, .y = {.value = 6789}}; + constexpr point_x px2{.x = {.value = 6789}, .y = {.value = 1234}}; + + constexpr point_x_equal pxe1{.x = {.value = 6789}, .y = {.value = 1234}}; + constexpr point_x_equal pxe2{.x = {.value = 1234}, .y = {.value = 6789}}; + + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs | rhs; + } + + template + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs.template bit_or(Index)>(rhs); + } + + [[nodiscard]] constexpr auto do_x_equal(const auto& lhs, const auto& rhs) noexcept -> auto + { + auto t = lhs.copy(); + t |= rhs; + return t; + } + + template + [[nodiscard]] constexpr auto do_x_equal(const auto& lhs, const auto& rhs) noexcept -> auto + { + auto t = lhs.copy(); + t.template bit_or_equal(Index)>(rhs); + return t; + } + + // point_x | point_x + static_assert(std::ranges::all_of(do_x(px1, px2) == point{.x = px1.x.value | px2.x.value, .y = px1.y.value | px2.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, px2) == point{.x = px1.x.value | px2.x.value, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, px2) == point{.x = px1.x.value, .y = px1.y.value | px2.y.value}, std::identity{})); + // point_x | point + static_assert(std::ranges::all_of(do_x(px1, p) == point{.x = px1.x.value | p.x, .y = px1.y.value | p.y}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, p) == point{.x = px1.x.value | p.x, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, p) == point{.x = px1.x.value, .y = px1.y.value | p.y}, std::identity{})); + // point_x | value + static_assert(std::ranges::all_of(do_x(px1, 13579) == point{.x = px1.x.value | 13579, .y = px1.y.value | 13579}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, 13579) == point{.x = px1.x.value | 13579, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, 13579) == point{.x = px1.x.value, .y = px1.y.value | 13579}, std::identity{})); + // point_x_equal |= point_x_equal + static_assert(std::ranges::all_of(do_x_equal(pxe1, pxe2) == point{.x = pxe1.x.value | pxe2.x.value, .y = pxe1.y.value | pxe2.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, pxe2) == point{.x = pxe1.x.value | pxe2.x.value, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, pxe2) == point{.x = pxe1.x.value, .y = pxe1.y.value | pxe2.y.value}, std::identity{})); + // point_x_equal |= point + static_assert(std::ranges::all_of(do_x_equal(pxe1, p) == point{.x = pxe1.x.value | p.x, .y = pxe1.y.value | p.y}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, p) == point{.x = pxe1.x.value | p.x, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, p) == point{.x = pxe1.x.value, .y = pxe1.y.value | p.y}, std::identity{})); + // point_x_equal |= value + static_assert(std::ranges::all_of(do_x_equal(pxe1, 13579) == point{.x = pxe1.x.value | 13579, .y = pxe1.y.value | 13579}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, 13579) == point{.x = pxe1.x.value | 13579, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, 13579) == point{.x = pxe1.x.value, .y = pxe1.y.value | 13579}, std::identity{})); + } + + namespace bit_xor + { + struct wrapper_x + { + int value; + + // point_x == point ==> point_x.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + [[nodiscard]] constexpr auto operator^(const wrapper_x& other) const noexcept -> wrapper_x + { + return {.value = value ^ other.value}; + } + + [[nodiscard]] constexpr auto operator^(const int& other) const noexcept -> wrapper_x + { + return {.value = value ^ other}; + } + }; + + struct wrapper_x_equal + { + int value; + + // point_x_equal == point ==> point_x_equal.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + constexpr auto operator^=(const wrapper_x_equal& other) noexcept -> wrapper_x_equal& + { + value ^= other.value; + return *this; + } + + constexpr auto operator^=(const int& other) noexcept -> wrapper_x_equal& + { + value ^= other; + return *this; + } + }; + + struct point + { + int x; + int y; + }; + + struct point_x : meta::dimension + { + wrapper_x x; + wrapper_x y; + }; + + struct point_x_equal : meta::dimension + { + wrapper_x_equal x; + wrapper_x_equal y; + }; + + static_assert(sizeof(point) == sizeof(int) * 2); + static_assert(sizeof(point) == sizeof(point_x)); + static_assert(sizeof(point) == sizeof(point_x_equal)); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + + template + constexpr auto can_x = requires { std::declval() = std::declval() ^ std::declval(); }; + static_assert(can_x); + static_assert(not can_x); + + template + constexpr auto can_x_equal = requires { std::declval() ^= std::declval(); }; + static_assert(not can_x_equal); + static_assert(can_x_equal); + + constexpr point p{.x = 42, .y = 1337}; + + constexpr point_x px1{.x = {.value = 1234}, .y = {.value = 6789}}; + constexpr point_x px2{.x = {.value = 6789}, .y = {.value = 1234}}; + + constexpr point_x_equal pxe1{.x = {.value = 6789}, .y = {.value = 1234}}; + constexpr point_x_equal pxe2{.x = {.value = 1234}, .y = {.value = 6789}}; + + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs ^ rhs; + } + + template + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs.template bit_xor(Index)>(rhs); + } + + [[nodiscard]] constexpr auto do_x_equal(const auto& lhs, const auto& rhs) noexcept -> auto + { + auto t = lhs.copy(); + t ^= rhs; + return t; + } + + template + [[nodiscard]] constexpr auto do_x_equal(const auto& lhs, const auto& rhs) noexcept -> auto + { + auto t = lhs.copy(); + t.template bit_xor_equal(Index)>(rhs); + return t; + } + + // point_x ^ point_x + static_assert(std::ranges::all_of(do_x(px1, px2) == point{.x = px1.x.value ^ px2.x.value, .y = px1.y.value ^ px2.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, px2) == point{.x = px1.x.value ^ px2.x.value, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, px2) == point{.x = px1.x.value, .y = px1.y.value ^ px2.y.value}, std::identity{})); + // point_x ^ point + static_assert(std::ranges::all_of(do_x(px1, p) == point{.x = px1.x.value ^ p.x, .y = px1.y.value ^ p.y}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, p) == point{.x = px1.x.value ^ p.x, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, p) == point{.x = px1.x.value, .y = px1.y.value ^ p.y}, std::identity{})); + // point_x ^ value + static_assert(std::ranges::all_of(do_x(px1, 13579) == point{.x = px1.x.value ^ 13579, .y = px1.y.value ^ 13579}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px1, 13579) == point{.x = px1.x.value ^ 13579, .y = px1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px1, 13579) == point{.x = px1.x.value, .y = px1.y.value ^ 13579}, std::identity{})); + // point_x_equal ^= point_x_equal + static_assert(std::ranges::all_of(do_x_equal(pxe1, pxe2) == point{.x = pxe1.x.value ^ pxe2.x.value, .y = pxe1.y.value ^ pxe2.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, pxe2) == point{.x = pxe1.x.value ^ pxe2.x.value, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, pxe2) == point{.x = pxe1.x.value, .y = pxe1.y.value ^ pxe2.y.value}, std::identity{})); + // point_x_equal ^= point + static_assert(std::ranges::all_of(do_x_equal(pxe1, p) == point{.x = pxe1.x.value ^ p.x, .y = pxe1.y.value ^ p.y}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, p) == point{.x = pxe1.x.value ^ p.x, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, p) == point{.x = pxe1.x.value, .y = pxe1.y.value ^ p.y}, std::identity{})); + // point_x_equal ^= value + static_assert(std::ranges::all_of(do_x_equal(pxe1, 13579) == point{.x = pxe1.x.value ^ 13579, .y = pxe1.y.value ^ 13579}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<0>(pxe1, 13579) == point{.x = pxe1.x.value ^ 13579, .y = pxe1.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x_equal<1>(pxe1, 13579) == point{.x = pxe1.x.value, .y = pxe1.y.value ^ 13579}, std::identity{})); + } + + namespace bit_flip + { + struct wrapper_x + { + int value; + + // point_x == point ==> point_x.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + [[nodiscard]] constexpr auto operator~() const noexcept -> wrapper_x + { + return {.value = ~value}; + } + }; + + struct point + { + [[maybe_unused]] int x; + [[maybe_unused]] int y; + }; + + struct point_x : meta::dimension + { + [[maybe_unused]] wrapper_x x; + [[maybe_unused]] wrapper_x y; + }; + + static_assert(sizeof(point) == sizeof(int) * 2); + static_assert(sizeof(point) == sizeof(point_x)); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + + template + constexpr auto can_x = requires { std::declval() = ~std::declval(); }; + static_assert(can_x); + + constexpr point_x px{.x = {.value = 1234}, .y = {.value = 6789}}; + + [[nodiscard]] constexpr auto do_x(const auto& self) noexcept -> auto + { + return ~self; + } + + template + [[nodiscard]] constexpr auto do_x(const auto& self) noexcept -> auto + { + return self.template bit_flip(Index)>(); + } + + // ~point_x + static_assert(std::ranges::all_of(do_x(px) == point{.x = ~px.x.value, .y = ~px.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<0>(px) == point{.x = ~px.x.value, .y = px.y.value}, std::identity{})); + static_assert(std::ranges::all_of(do_x<1>(px) == point{.x = px.x.value, .y = ~px.y.value}, std::identity{})); + } + + namespace logical_and + { + struct wrapper_x + { + int value; + + // point_x == point ==> point_x.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + [[nodiscard]] constexpr auto operator and(const wrapper_x& other) const noexcept -> bool + { + return value and other.value; + } + + [[nodiscard]] constexpr auto operator and(const int& other) const noexcept -> bool + { + return value and other; + } + }; + + struct point + { + [[maybe_unused]] int x; + [[maybe_unused]] int y; + }; + + struct point_x : meta::dimension + { + [[maybe_unused]] wrapper_x x; + [[maybe_unused]] wrapper_x y; + }; + + static_assert(sizeof(point) == sizeof(int) * 2); + static_assert(sizeof(point) == sizeof(point_x)); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + + template + constexpr auto can_x = requires { std::declval() and std::declval(); }; + static_assert(can_x); + + constexpr point p{.x = 42, .y = 1337}; + + constexpr point_x px1{.x = {.value = 1234}, .y = {.value = 6789}}; + constexpr point_x px2{.x = {.value = 6789}, .y = {.value = 1234}}; + + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs and rhs; + } + + // point_x and point_x + static_assert(std::ranges::all_of(do_x(px1, px2), std::identity{})); + // point_x and point + static_assert(std::ranges::all_of(do_x(px1, p), std::identity{})); + // point_x and value + static_assert(std::ranges::all_of(do_x(px1, 13579), std::identity{})); + } + + namespace logical_or + { + struct wrapper_x + { + int value; + + // point_x == point ==> point_x.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + [[nodiscard]] constexpr auto operator||(const wrapper_x& other) const noexcept -> bool + { + return value || other.value; + } + + [[nodiscard]] constexpr auto operator||(const int& other) const noexcept -> bool + { + return value || other; + } + }; + + struct point + { + [[maybe_unused]] int x; + [[maybe_unused]] int y; + }; + + struct point_x : meta::dimension + { + [[maybe_unused]] wrapper_x x; + [[maybe_unused]] wrapper_x y; + }; + + static_assert(sizeof(point) == sizeof(int) * 2); + static_assert(sizeof(point) == sizeof(point_x)); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + + template + constexpr auto can_x = requires { std::declval() or std::declval(); }; + static_assert(can_x); + + constexpr point p{.x = 42, .y = 1337}; + + constexpr point_x px1{.x = {.value = 1234}, .y = {.value = 6789}}; + constexpr point_x px2{.x = {.value = 6789}, .y = {.value = 1234}}; + + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs or rhs; + } + + // point_x or point_x + static_assert(std::ranges::all_of(do_x(px1, px2), std::identity{})); + // point_x or point + static_assert(std::ranges::all_of(do_x(px1, p), std::identity{})); + // point_x or value + static_assert(std::ranges::all_of(do_x(px1, 13579), std::identity{})); + } + + namespace logical_not + { + struct wrapper_x + { + int value; + + // point_x == point ==> point_x.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + [[nodiscard]] constexpr auto operator not() const noexcept -> bool + { + return not value; + } + }; + + struct point + { + [[maybe_unused]] int x; + [[maybe_unused]] int y; + }; + + struct point_x : meta::dimension + { + [[maybe_unused]] wrapper_x x; + [[maybe_unused]] wrapper_x y; + }; + + static_assert(sizeof(point) == sizeof(int) * 2); + static_assert(sizeof(point) == sizeof(point_x)); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + + template + constexpr auto can_x = requires { not std::declval(); }; + static_assert(can_x); + + constexpr point_x px{.x = {.value = 1234}, .y = {.value = 6789}}; + + // not point_x + static_assert(std::ranges::none_of(not px, std::identity{})); + } + + namespace greater_than + { + struct wrapper_x + { + int value; + + // point_x == point ==> point_x.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + [[nodiscard]] constexpr auto operator>(const wrapper_x& other) const noexcept -> bool + { + return value > other.value; + } + + [[nodiscard]] constexpr auto operator>(const int& other) const noexcept -> bool + { + return value > other; + } + }; + + struct point + { + [[maybe_unused]] int x; + [[maybe_unused]] int y; + }; + + struct point_x : meta::dimension + { + [[maybe_unused]] wrapper_x x; + [[maybe_unused]] wrapper_x y; + }; + + static_assert(sizeof(point) == sizeof(int) * 2); + static_assert(sizeof(point) == sizeof(point_x)); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + + template + constexpr auto can_x = requires { std::declval() > std::declval(); }; + static_assert(can_x); + + constexpr point p{.x = 42, .y = 1337}; + + constexpr point_x px1{.x = {.value = 12340}, .y = {.value = 6789}}; + constexpr point_x px2{.x = {.value = 6789}, .y = {.value = 1234}}; + + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs > rhs; + } + + // point_x > point_x + static_assert(std::ranges::all_of(do_x(px1, px2), std::identity{})); + // point_x > point + static_assert(std::ranges::all_of(do_x(px1, p), std::identity{})); + // point_x > value + static_assert(std::ranges::all_of(do_x(px1, 1357), std::identity{})); + } + + namespace greater_equal + { + struct wrapper_x + { + int value; + + // point_x == point ==> point_x.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + [[nodiscard]] constexpr auto operator>=(const wrapper_x& other) const noexcept -> bool + { + return value >= other.value; + } + + [[nodiscard]] constexpr auto operator>=(const int& other) const noexcept -> bool + { + return value >= other; + } + }; + + struct point + { + [[maybe_unused]] int x; + [[maybe_unused]] int y; + }; + + struct point_x : meta::dimension + { + [[maybe_unused]] wrapper_x x; + [[maybe_unused]] wrapper_x y; + }; + + static_assert(sizeof(point) == sizeof(int) * 2); + static_assert(sizeof(point) == sizeof(point_x)); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + + template + constexpr auto can_x = requires { std::declval() >= std::declval(); }; + static_assert(can_x); + + constexpr point p{.x = 42, .y = 1337}; + + constexpr point_x px1{.x = {.value = 12340}, .y = {.value = 6789}}; + constexpr point_x px2{.x = {.value = 6789}, .y = {.value = 1234}}; + + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs >= rhs; + } + + // point_x >= point_x + static_assert(std::ranges::all_of(do_x(px1, px2), std::identity{})); + // point_x >= point + static_assert(std::ranges::all_of(do_x(px1, p), std::identity{})); + // point_x >= value + static_assert(std::ranges::all_of(do_x(px1, 1357), std::identity{})); + } + + namespace less_than + { + struct wrapper_x + { + int value; + + // point_x == point ==> point_x.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + [[nodiscard]] constexpr auto operator<(const wrapper_x& other) const noexcept -> bool + { + return value < other.value; + } + + [[nodiscard]] constexpr auto operator<(const int& other) const noexcept -> bool + { + return value < other; + } + }; + + struct point + { + [[maybe_unused]] int x; + [[maybe_unused]] int y; + }; + + struct point_x : meta::dimension + { + [[maybe_unused]] wrapper_x x; + [[maybe_unused]] wrapper_x y; + }; + + static_assert(sizeof(point) == sizeof(int) * 2); + static_assert(sizeof(point) == sizeof(point_x)); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + + template + constexpr auto can_x = requires { std::declval() < std::declval(); }; + static_assert(can_x); + + constexpr point p{.x = 4200, .y = 13370}; + + constexpr point_x px1{.x = {.value = 1234}, .y = {.value = 6789}}; + constexpr point_x px2{.x = {.value = 6789}, .y = {.value = 12340}}; + + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs < rhs; + } + + // point_x < point_x + static_assert(std::ranges::all_of(do_x(px1, px2), std::identity{})); + // point_x < point + static_assert(std::ranges::all_of(do_x(px1, p), std::identity{})); + // point_x < value + static_assert(std::ranges::all_of(do_x(px1, 13579), std::identity{})); + } + + namespace less_equal + { + struct wrapper_x + { + int value; + + // point_x == point ==> point_x.members == point.members ==> x == int + [[nodiscard]] constexpr auto operator==(const int other) const noexcept -> bool + { + return value == other; + } + + [[nodiscard]] constexpr auto operator<=(const wrapper_x& other) const noexcept -> bool + { + return value <= other.value; + } + + [[nodiscard]] constexpr auto operator<=(const int& other) const noexcept -> bool + { + return value <= other; + } + }; + + struct point + { + [[maybe_unused]] int x; + [[maybe_unused]] int y; + }; + + struct point_x : meta::dimension + { + [[maybe_unused]] wrapper_x x; + [[maybe_unused]] wrapper_x y; + }; + + static_assert(sizeof(point) == sizeof(int) * 2); + static_assert(sizeof(point) == sizeof(point_x)); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_constructible_v); + + template + constexpr auto can_x = requires { std::declval() <= std::declval(); }; + static_assert(can_x); + + constexpr point p{.x = 4200, .y = 13370}; + + constexpr point_x px1{.x = {.value = 1234}, .y = {.value = 6789}}; + constexpr point_x px2{.x = {.value = 6789}, .y = {.value = 12340}}; + + [[nodiscard]] constexpr auto do_x(const auto& lhs, const auto& rhs) noexcept -> auto + { + return lhs <= rhs; + } + + // point_x <= point_x + static_assert(std::ranges::all_of(do_x(px1, px2), std::identity{})); + // point_x <= point + static_assert(std::ranges::all_of(do_x(px1, p), std::identity{})); + // point_x <= value + static_assert(std::ranges::all_of(do_x(px1, 13579), std::identity{})); + } + + GAL_PROMETHEUS_COMPILER_DISABLE_WARNING_POP +} diff --git a/unit_test/src/meta/enumeration.cpp b/unit_test/src/meta/enumeration.cpp index 828a2c06..bb317bf4 100644 --- a/unit_test/src/meta/enumeration.cpp +++ b/unit_test/src/meta/enumeration.cpp @@ -1,17 +1,13 @@ -#include - -#if GAL_PROMETHEUS_USE_MODULE -import std; -import gal.prometheus; -#else -#include -#endif +// meta::enumeration +#include +// functional::enumeration +#include using namespace gal::prometheus; namespace { - enum FreeEnum0 + enum FreeEnum0 : std::uint8_t { FE0_E1 = 0, FE0_E2 = 1, @@ -19,7 +15,7 @@ namespace FE0_E4 = 3, }; - enum FreeEnum1 + enum FreeEnum1: std::uint8_t { FE1_E1 = 1, FE1_E2 = 2, @@ -27,7 +23,7 @@ namespace FE1_E4 = 4, }; - enum class ScopedEnum0 + enum class ScopedEnum0: std::uint8_t { E1 = 0, E2 = 1, @@ -35,7 +31,7 @@ namespace E4 = 3, }; - enum class ScopedEnum1 + enum class ScopedEnum1: std::uint8_t { E1 = 1, E2 = 2, @@ -43,7 +39,7 @@ namespace E4 = 4, }; - enum FreeFlag0 + enum FreeFlag0: std::uint8_t { FF0_F0 = 0b0000, FF0_F1 = 0b0001, @@ -55,7 +51,7 @@ namespace FF0_F6 = FF0_F3 | FF0_F4, }; - enum FreeFlag1 + enum FreeFlag1: std::uint8_t { FF1_F1 = 0b0001, FF1_F2 = 0b0010, @@ -66,7 +62,7 @@ namespace FF1_F6 = FF1_F3 | FF1_F4, }; - enum class ScopedFlag0 + enum class ScopedFlag0: std::uint8_t { F0 = 0b0000, F1 = 0b0001, @@ -76,9 +72,11 @@ namespace F5 = F1 | F2, F6 = F3 | F4, + + PROMETHEUS_MAGIC_ENUM_FLAG [[maybe_unused]] }; - enum class ScopedFlag1 + enum class ScopedFlag1: std::uint8_t { F1 = 0b0001, F2 = 0b0010, @@ -87,327 +85,211 @@ namespace F5 = F1 | F2, F6 = F3 | F4, + + PROMETHEUS_MAGIC_ENUM_FLAG [[maybe_unused]] }; - GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"meta.enumeration"> _ = [] + // ========================================================================== + // min/max + + static_assert(meta::min_value_of() == FE0_E1); + static_assert(meta::max_value_of() == FE0_E4); + + static_assert(meta::min_value_of() == FE1_E1); + static_assert(meta::max_value_of() == FE1_E4); + + static_assert(meta::min_value_of() == std::to_underlying(ScopedEnum0::E1)); + static_assert(meta::max_value_of() == std::to_underlying(ScopedEnum0::E4)); + + static_assert(meta::min_value_of() == std::to_underlying(ScopedEnum1::E1)); + static_assert(meta::max_value_of() == std::to_underlying(ScopedEnum1::E4)); + + static_assert(meta::min_value_of() == FF0_F0); + static_assert(meta::max_value_of() == FF0_F6); + + static_assert(meta::min_value_of() == FF1_F1); + static_assert(meta::max_value_of() == FF1_F6); + + static_assert(meta::min_value_of() == std::to_underlying(ScopedFlag0::F0)); + static_assert(meta::max_value_of() == std::to_underlying(ScopedFlag0::F6)); + + static_assert(meta::min_value_of() == std::to_underlying(ScopedFlag1::F1)); + static_assert(meta::max_value_of() == std::to_underlying(ScopedFlag1::F6)); + + // ========================================================================== + // name_of + + static_assert(meta::name_of(FE0_E1) == "FE0_E1"); + static_assert(meta::name_of(FE0_E2) == "FE0_E2"); + static_assert(meta::name_of(FE0_E3) == "FE0_E3"); + static_assert(meta::name_of(FE0_E4) == "FE0_E4"); + static_assert(meta::name_of(static_cast(FE0_E4 + 1)) == meta::enum_name_not_found); + static_assert(meta::name_of(FE1_E1) == "FE1_E1"); + static_assert(meta::name_of(FE1_E2) == "FE1_E2"); + static_assert(meta::name_of(FE1_E3) == "FE1_E3"); + static_assert(meta::name_of(FE1_E4) == "FE1_E4"); + static_assert(meta::name_of(static_cast(FE1_E4 + 1)) == meta::enum_name_not_found); + + static_assert(meta::name_of(ScopedEnum0::E1) == "ScopedEnum0::E1"); + static_assert(meta::name_of(ScopedEnum0::E2) == "ScopedEnum0::E2"); + static_assert(meta::name_of(ScopedEnum0::E3) == "ScopedEnum0::E3"); + static_assert(meta::name_of(ScopedEnum0::E4) == "ScopedEnum0::E4"); + static_assert(meta::name_of(static_cast(std::to_underlying(ScopedEnum0::E4) + 1)) == meta::enum_name_not_found); + static_assert(meta::name_of(ScopedEnum1::E1) == "ScopedEnum1::E1"); + static_assert(meta::name_of(ScopedEnum1::E2) == "ScopedEnum1::E2"); + static_assert(meta::name_of(ScopedEnum1::E3) == "ScopedEnum1::E3"); + static_assert(meta::name_of(ScopedEnum1::E4) == "ScopedEnum1::E4"); + static_assert(meta::name_of(static_cast(std::to_underlying(ScopedEnum1::E4) + 1)) == meta::enum_name_not_found); + + static_assert(meta::name_of(FF0_F0) == "FF0_F0"); + static_assert(meta::name_of(FF0_F1) == "FF0_F1"); + static_assert(meta::name_of(FF0_F2) == "FF0_F2"); + static_assert(meta::name_of(FF0_F3) == "FF0_F3"); + static_assert(meta::name_of(FF0_F4) == "FF0_F4"); + static_assert(meta::name_of(FF0_F5) == "FF0_F5"); + static_assert(meta::name_of(FF0_F1 | FF0_F2) == "FF0_F5"); + static_assert(meta::name_of(FF0_F6) == "FF0_F6"); + static_assert(meta::name_of(FF0_F3 | FF0_F4) == "FF0_F6"); + static_assert(meta::name_of(static_cast(FF0_F6 + 1)) == meta::enum_name_not_found); + + static_assert(meta::name_of(FF1_F1) == "FF1_F1"); + static_assert(meta::name_of(FF1_F2) == "FF1_F2"); + static_assert(meta::name_of(FF1_F3) == "FF1_F3"); + static_assert(meta::name_of(FF1_F4) == "FF1_F4"); + static_assert(meta::name_of(FF1_F5) == "FF1_F5"); + static_assert(meta::name_of(FF1_F1 | FF1_F2) == "FF1_F5"); + static_assert(meta::name_of(FF1_F6) == "FF1_F6"); + static_assert(meta::name_of(FF1_F3 | FF1_F4) == "FF1_F6"); + static_assert(meta::name_of(static_cast(FF1_F6 + 1)) == meta::enum_name_not_found); + + static_assert(meta::name_of(ScopedFlag0::F0) == "ScopedFlag0::F0"); + static_assert(meta::name_of(ScopedFlag0::F1) == "ScopedFlag0::F1"); + static_assert(meta::name_of(ScopedFlag0::F2) == "ScopedFlag0::F2"); + static_assert(meta::name_of(ScopedFlag0::F3) == "ScopedFlag0::F3"); + static_assert(meta::name_of(ScopedFlag0::F4) == "ScopedFlag0::F4"); + static_assert(meta::name_of(ScopedFlag0::F5) == "ScopedFlag0::F5"); + static_assert(meta::name_of(ScopedFlag0::F1 | ScopedFlag0::F2) == "ScopedFlag0::F5"); + static_assert(meta::name_of(ScopedFlag0::F6) == "ScopedFlag0::F6"); + static_assert(meta::name_of(ScopedFlag0::F3 | ScopedFlag0::F4) == "ScopedFlag0::F6"); + static_assert(meta::name_of(static_cast(std::to_underlying(ScopedFlag0::F6) + 1)) == meta::enum_name_not_found); + + static_assert(meta::name_of(ScopedFlag1::F1) == "ScopedFlag1::F1"); + static_assert(meta::name_of(ScopedFlag1::F2) == "ScopedFlag1::F2"); + static_assert(meta::name_of(ScopedFlag1::F3) == "ScopedFlag1::F3"); + static_assert(meta::name_of(ScopedFlag1::F4) == "ScopedFlag1::F4"); + static_assert(meta::name_of(ScopedFlag1::F5) == "ScopedFlag1::F5"); + static_assert(meta::name_of(ScopedFlag1::F1 | ScopedFlag1::F2) == "ScopedFlag1::F5"); + static_assert(meta::name_of(ScopedFlag1::F6) == "ScopedFlag1::F6"); + static_assert(meta::name_of(ScopedFlag1::F3 | ScopedFlag1::F4) == "ScopedFlag1::F6"); + static_assert(meta::name_of(static_cast(std::to_underlying(ScopedFlag1::F6) + 1)) == meta::enum_name_not_found); + + // ========================================================================== + // full_name_of + + template + [[nodiscard]] constexpr auto test_full_name_of(const auto e, const auto expected, const auto split) noexcept -> bool { - using namespace unit_test; - using namespace literals; - - "min/max"_test = [] - { - static_assert(meta::min_value_of() == FE0_E1); - static_assert(meta::max_value_of() == FE0_E4); - - static_assert(meta::min_value_of() == FE1_E1); - static_assert(meta::max_value_of() == FE1_E4); - - static_assert(meta::min_value_of() == std::to_underlying(ScopedEnum0::E1)); - static_assert(meta::max_value_of() == std::to_underlying(ScopedEnum0::E4)); - - static_assert(meta::min_value_of() == std::to_underlying(ScopedEnum1::E1)); - static_assert(meta::max_value_of() == std::to_underlying(ScopedEnum1::E4)); - - static_assert(meta::min_value_of() == FF0_F0); - static_assert(meta::max_value_of() == FF0_F6); - - static_assert(meta::min_value_of() == FF1_F1); - static_assert(meta::max_value_of() == FF1_F6); - - static_assert(meta::min_value_of() == std::to_underlying(ScopedFlag0::F0)); - static_assert(meta::max_value_of() == std::to_underlying(ScopedFlag0::F6)); - - static_assert(meta::min_value_of() == std::to_underlying(ScopedFlag1::F1)); - static_assert(meta::max_value_of() == std::to_underlying(ScopedFlag1::F6)); - }; - - "name_of"_test = [] - { - "FreeEnum0"_test = [] - { - static_assert(meta::name_of(FE0_E1) == "FE0_E1"); - static_assert(meta::name_of(FE0_E2) == "FE0_E2"); - static_assert(meta::name_of(FE0_E3) == "FE0_E3"); - static_assert(meta::name_of(FE0_E4) == "FE0_E4"); - static_assert(meta::name_of(static_cast(FE0_E4 + 1)) == meta::enum_name_not_found); - }; - - "FreeEnum1"_test = [] - { - static_assert(meta::name_of(FE1_E1) == "FE1_E1"); - static_assert(meta::name_of(FE1_E2) == "FE1_E2"); - static_assert(meta::name_of(FE1_E3) == "FE1_E3"); - static_assert(meta::name_of(FE1_E4) == "FE1_E4"); - static_assert(meta::name_of(static_cast(FE1_E4 + 1)) == meta::enum_name_not_found); - }; - - "ScopedEnum0"_test = [] - { - static_assert(meta::name_of(ScopedEnum0::E1) == "ScopedEnum0::E1"); - static_assert(meta::name_of(ScopedEnum0::E2) == "ScopedEnum0::E2"); - static_assert(meta::name_of(ScopedEnum0::E3) == "ScopedEnum0::E3"); - static_assert(meta::name_of(ScopedEnum0::E4) == "ScopedEnum0::E4"); - static_assert(meta::name_of(static_cast(std::to_underlying(ScopedEnum0::E4) + 1)) == meta::enum_name_not_found); - }; - - "ScopedEnum1"_test = [] - { - static_assert(meta::name_of(ScopedEnum1::E1) == "ScopedEnum1::E1"); - static_assert(meta::name_of(ScopedEnum1::E2) == "ScopedEnum1::E2"); - static_assert(meta::name_of(ScopedEnum1::E3) == "ScopedEnum1::E3"); - static_assert(meta::name_of(ScopedEnum1::E4) == "ScopedEnum1::E4"); - static_assert(meta::name_of(static_cast(std::to_underlying(ScopedEnum1::E4) + 1)) == meta::enum_name_not_found); - }; - - "FreeFlag0"_test = [] - { - static_assert(meta::name_of(FF0_F0) == "FF0_F0"); - static_assert(meta::name_of(FF0_F1) == "FF0_F1"); - static_assert(meta::name_of(FF0_F2) == "FF0_F2"); - static_assert(meta::name_of(FF0_F3) == "FF0_F3"); - static_assert(meta::name_of(FF0_F4) == "FF0_F4"); - - static_assert(meta::name_of(FF0_F5) == "FF0_F5"); - static_assert(meta::name_of(FF0_F1 | FF0_F2) == "FF0_F5"); - static_assert(meta::name_of(FF0_F6) == "FF0_F6"); - static_assert(meta::name_of(FF0_F3 | FF0_F4) == "FF0_F6"); - - static_assert(meta::name_of(static_cast(FF0_F6 + 1)) == meta::enum_name_not_found); - }; - - "FreeFlag1"_test = [] - { - static_assert(meta::name_of(FF1_F1) == "FF1_F1"); - static_assert(meta::name_of(FF1_F2) == "FF1_F2"); - static_assert(meta::name_of(FF1_F3) == "FF1_F3"); - static_assert(meta::name_of(FF1_F4) == "FF1_F4"); - - static_assert(meta::name_of(FF1_F5) == "FF1_F5"); - static_assert(meta::name_of(FF1_F1 | FF1_F2) == "FF1_F5"); - static_assert(meta::name_of(FF1_F6) == "FF1_F6"); - static_assert(meta::name_of(FF1_F3 | FF1_F4) == "FF1_F6"); - - static_assert(meta::name_of(static_cast(FF1_F6 + 1)) == meta::enum_name_not_found); - }; - - "ScopedFlag0"_test = [] - { - static_assert(meta::name_of(ScopedFlag0::F0) == "ScopedFlag0::F0"); - static_assert(meta::name_of(ScopedFlag0::F1) == "ScopedFlag0::F1"); - static_assert(meta::name_of(ScopedFlag0::F2) == "ScopedFlag0::F2"); - static_assert(meta::name_of(ScopedFlag0::F3) == "ScopedFlag0::F3"); - static_assert(meta::name_of(ScopedFlag0::F4) == "ScopedFlag0::F4"); - - using functional::operators::operator|; - - static_assert(meta::name_of(ScopedFlag0::F5) == "ScopedFlag0::F5"); - static_assert(meta::name_of(ScopedFlag0::F1 | ScopedFlag0::F2) == "ScopedFlag0::F5"); - static_assert(meta::name_of(ScopedFlag0::F6) == "ScopedFlag0::F6"); - static_assert(meta::name_of(ScopedFlag0::F3 | ScopedFlag0::F4) == "ScopedFlag0::F6"); - - static_assert(meta::name_of(static_cast(std::to_underlying(ScopedFlag0::F6) + 1)) == meta::enum_name_not_found); - }; - - "ScopedFlag1"_test = [] - { - static_assert(meta::name_of(ScopedFlag1::F1) == "ScopedFlag1::F1"); - static_assert(meta::name_of(ScopedFlag1::F2) == "ScopedFlag1::F2"); - static_assert(meta::name_of(ScopedFlag1::F3) == "ScopedFlag1::F3"); - static_assert(meta::name_of(ScopedFlag1::F4) == "ScopedFlag1::F4"); - - using functional::operators::operator|; - - static_assert(meta::name_of(ScopedFlag1::F5) == "ScopedFlag1::F5"); - static_assert(meta::name_of(ScopedFlag1::F1 | ScopedFlag1::F2) == "ScopedFlag1::F5"); - static_assert(meta::name_of(ScopedFlag1::F6) == "ScopedFlag1::F6"); - static_assert(meta::name_of(ScopedFlag1::F3 | ScopedFlag1::F4) == "ScopedFlag1::F6"); - - static_assert(meta::name_of(static_cast(std::to_underlying(ScopedFlag1::F6) + 1)) == meta::enum_name_not_found); - }; - - "full_name_of"_test = [] - { - "FreeFlag0"_test = [] - { - using namespace unit_test::operators; - - constexpr std::string_view split{"-"}; - - expect((meta::full_name_of(FF0_F5, split) == "FF0_F1-FF0_F2") == "FF0_F5 == FF0_F1-FF0_F2"_b); - expect((meta::full_name_of(FF0_F6, split) == "FF0_F3-FF0_F4") == "FF0_F6 == FF0_F3-FF0_F4"_b); - }; - - "FreeFlag1"_test = [] - { - using namespace unit_test::operators; - - constexpr std::string_view split{"/"}; - - expect((meta::full_name_of(FF1_F5, split) == "FF1_F1/FF1_F2") == "FF1_F5 == FF1_F1/FF1_F2"_b); - expect((meta::full_name_of(FF1_F6, split) == "FF1_F3/FF1_F4") == "FF1_F6 == FF1_F3/FF1_F4"_b); - }; - - "ScopedFlag0"_test = [] - { - using namespace unit_test::operators; - - constexpr std::string_view split{"-"}; - - expect( - (meta::full_name_of(ScopedFlag0::F5, split) == "F1-F2") == - "ScopedFlag0::F5 == ScopedFlag0::F1-ScopedFlag0::F2"_b - ); - expect( - (meta::full_name_of(ScopedFlag0::F6, split) == "F3-F4") == - "ScopedFlag0::F6 == ScopedFlag0::F3-ScopedFlag0::F4"_b - ); - }; - - "ScopedFlag1"_test = [] - { - using namespace unit_test::operators; - - constexpr std::string_view split{"/"}; - - expect( - (meta::full_name_of(ScopedFlag1::F5, split) == "ScopedFlag1::F1/ScopedFlag1::F2") == - "ScopedFlag1::F5 == ScopedFlag1::F1/ScopedFlag1::F2"_b - ); - expect( - (meta::full_name_of(ScopedFlag1::F6, split) == "ScopedFlag1::F3/ScopedFlag1::F4") == - "ScopedFlag1::F6 == ScopedFlag1::F3/ScopedFlag1::F4"_b - ); - }; - }; - }; - - "value_of"_test = [] - { - "FreeEnum0"_test = [] - { - static_assert(meta::value_of("FE0_E1") == FE0_E1); - static_assert(meta::value_of("FE0_E2") == FE0_E2); - static_assert(meta::value_of("FE0_E3") == FE0_E3); - static_assert(meta::value_of("FE0_E4") == FE0_E4); - static_assert(meta::value_of("FE0_E5", static_cast(42)) == 42); - }; - - "FreeEnum1"_test = [] - { - static_assert(meta::value_of("FE1_E1") == FE1_E1); - static_assert(meta::value_of("FE1_E2") == FE1_E2); - static_assert(meta::value_of("FE1_E3") == FE1_E3); - static_assert(meta::value_of("FE1_E4") == FE1_E4); - static_assert(meta::value_of("FE1_E5", static_cast(42)) == 42); - }; - - "ScopedEnum0"_test = [] - { - static_assert(meta::value_of("ScopedEnum0::E1") == ScopedEnum0::E1); - static_assert(meta::value_of("ScopedEnum0::E2") == ScopedEnum0::E2); - static_assert(meta::value_of("ScopedEnum0::E3") == ScopedEnum0::E3); - static_assert(meta::value_of("ScopedEnum0::E4") == ScopedEnum0::E4); - static_assert(meta::value_of("ScopedEnum0::E5") == static_cast(0)); - }; - - "ScopedEnum1"_test = [] - { - static_assert(meta::value_of("ScopedEnum1::E1") == ScopedEnum1::E1); - static_assert(meta::value_of("ScopedEnum1::E2") == ScopedEnum1::E2); - static_assert(meta::value_of("ScopedEnum1::E3") == ScopedEnum1::E3); - static_assert(meta::value_of("ScopedEnum1::E4") == ScopedEnum1::E4); - static_assert(meta::value_of("ScopedEnum1::E5") == static_cast(0)); - }; - - "FreeFlag0"_test = [] - { - static_assert(meta::value_of("FF0_F0") == FF0_F0); - static_assert(meta::value_of("FF0_F1") == FF0_F1); - static_assert(meta::value_of("FF0_F2") == FF0_F2); - static_assert(meta::value_of("FF0_F3") == FF0_F3); - static_assert(meta::value_of("FF0_F4") == FF0_F4); - - static_assert(meta::value_of("FF0_F5") == FF0_F5); - static_assert(meta::value_of("FF0_F1|FF0_F2") == FF0_F5); - static_assert(meta::value_of("FF0_F6") == FF0_F6); - static_assert(meta::value_of("FF0_F3|FF0_F4") == FF0_F6); - - static_assert(meta::value_of("FF0_F3|FF0_F1337", static_cast(42)) == 42); - static_assert(meta::value_of("FF0_F3|FF0_F1337") == FF0_F3); - - static_assert(meta::value_of("FF0_F7", static_cast(42)) == 42); - }; - - "FreeFlag1"_test = [] - { - static_assert(meta::value_of("FF1_F1") == FF1_F1); - static_assert(meta::value_of("FF1_F2") == FF1_F2); - static_assert(meta::value_of("FF1_F3") == FF1_F3); - static_assert(meta::value_of("FF1_F4") == FF1_F4); - - static_assert(meta::value_of("FF1_F5") == FF1_F5); - static_assert(meta::value_of("FF1_F1|FF1_F2") == FF1_F5); - static_assert(meta::value_of("FF1_F6") == FF1_F6); - static_assert(meta::value_of("FF1_F3|FF1_F4") == FF1_F6); - - static_assert(meta::value_of("FF1_F3|FF1_F1337", static_cast(42)) == 42); - static_assert(meta::value_of("FF1_F3|FF1_F1337") == FF1_F3); - - static_assert(meta::value_of("FF1_F7", static_cast(42)) == 42); - }; - - "ScopedFlag0"_test = [] - { - constexpr std::string_view split{"+"}; - - static_assert(meta::value_of("ScopedFlag0::F0", split) == ScopedFlag0::F0); - static_assert(meta::value_of("ScopedFlag0::F1", split) == ScopedFlag0::F1); - static_assert(meta::value_of("ScopedFlag0::F2", split) == ScopedFlag0::F2); - static_assert(meta::value_of("ScopedFlag0::F3", split) == ScopedFlag0::F3); - static_assert(meta::value_of("ScopedFlag0::F4", split) == ScopedFlag0::F4); - - static_assert(meta::value_of("ScopedFlag0::F5", split) == ScopedFlag0::F5); - static_assert(meta::value_of("ScopedFlag0::F1+ScopedFlag0::F2", split) == ScopedFlag0::F5); - static_assert(meta::value_of("ScopedFlag0::F6", split) == ScopedFlag0::F6); - static_assert(meta::value_of("ScopedFlag0::F3+ScopedFlag0::F4", split) == ScopedFlag0::F6); - - static_assert( - meta::value_of("ScopedFlag0::F3+ScopedFlag0::F1337", split, static_cast(42)) == - static_cast(42) - ); - static_assert(meta::value_of("ScopedFlag0::F3+ScopedFlag0::F1337", split) == ScopedFlag0::F3); - - static_assert( - meta::value_of("ScopedFlag0::F7", split, static_cast(42)) == - static_cast(42) - ); - }; - - "ScopedFlag1"_test = [] - { - constexpr std::string_view split{"-"}; - - static_assert(meta::value_of("ScopedFlag1::F1", split) == ScopedFlag1::F1); - static_assert(meta::value_of("ScopedFlag1::F2", split) == ScopedFlag1::F2); - static_assert(meta::value_of("ScopedFlag1::F3", split) == ScopedFlag1::F3); - static_assert(meta::value_of("ScopedFlag1::F4", split) == ScopedFlag1::F4); - - static_assert(meta::value_of("ScopedFlag1::F5", split) == ScopedFlag1::F5); - static_assert(meta::value_of("ScopedFlag1::F1-ScopedFlag1::F2", split) == ScopedFlag1::F5); - static_assert(meta::value_of("ScopedFlag1::F6", split) == ScopedFlag1::F6); - static_assert(meta::value_of("ScopedFlag1::F3-ScopedFlag1::F4", split) == ScopedFlag1::F6); - - static_assert( - meta::value_of("ScopedFlag1::F3-ScopedFlag1::F1337", split, static_cast(42)) == - static_cast(42) - ); - static_assert(meta::value_of("ScopedFlag1::F3-ScopedFlag1::F1337", split) == ScopedFlag1::F3); - - static_assert( - meta::value_of("ScopedFlag1::F7", split, static_cast(42)) == - static_cast(42) - ); - }; - }; - }; + return meta::full_name_of(e, split) == expected; + } + + static_assert(test_full_name_of(FF0_F5, "FF0_F1-FF0_F2", "-")); + static_assert(test_full_name_of(FF0_F6, "FF0_F3-FF0_F4", "-")); + + static_assert(test_full_name_of(FF1_F5, "FF1_F1/FF1_F2", "/")); + static_assert(test_full_name_of(FF1_F6, "FF1_F3/FF1_F4", "/")); + + static_assert(test_full_name_of(ScopedFlag0::F5, "F1-F2", "-")); + static_assert(test_full_name_of(ScopedFlag0::F6, "F3-F4", "-")); + + static_assert(test_full_name_of(ScopedFlag1::F5, "ScopedFlag1::F1/ScopedFlag1::F2", "/")); + static_assert(test_full_name_of(ScopedFlag1::F6, "ScopedFlag1::F3/ScopedFlag1::F4", "/")); + + // ========================================================================== + // value_of + + static_assert(meta::value_of("FE0_E1") == FE0_E1); + static_assert(meta::value_of("FE0_E2") == FE0_E2); + static_assert(meta::value_of("FE0_E3") == FE0_E3); + static_assert(meta::value_of("FE0_E4") == FE0_E4); + static_assert(meta::value_of("FE0_E5", static_cast(42)) == 42); + + static_assert(meta::value_of("FE1_E1") == FE1_E1); + static_assert(meta::value_of("FE1_E2") == FE1_E2); + static_assert(meta::value_of("FE1_E3") == FE1_E3); + static_assert(meta::value_of("FE1_E4") == FE1_E4); + static_assert(meta::value_of("FE1_E5", static_cast(42)) == 42); + + static_assert(meta::value_of("ScopedEnum0::E1") == ScopedEnum0::E1); + static_assert(meta::value_of("ScopedEnum0::E2") == ScopedEnum0::E2); + static_assert(meta::value_of("ScopedEnum0::E3") == ScopedEnum0::E3); + static_assert(meta::value_of("ScopedEnum0::E4") == ScopedEnum0::E4); + static_assert(meta::value_of("ScopedEnum0::E5") == static_cast(0)); + + static_assert(meta::value_of("ScopedEnum1::E1") == ScopedEnum1::E1); + static_assert(meta::value_of("ScopedEnum1::E2") == ScopedEnum1::E2); + static_assert(meta::value_of("ScopedEnum1::E3") == ScopedEnum1::E3); + static_assert(meta::value_of("ScopedEnum1::E4") == ScopedEnum1::E4); + static_assert(meta::value_of("ScopedEnum1::E5") == static_cast(0)); + + static_assert(meta::value_of("FF0_F0") == FF0_F0); + static_assert(meta::value_of("FF0_F1") == FF0_F1); + static_assert(meta::value_of("FF0_F2") == FF0_F2); + static_assert(meta::value_of("FF0_F3") == FF0_F3); + static_assert(meta::value_of("FF0_F4") == FF0_F4); + static_assert(meta::value_of("FF0_F5") == FF0_F5); + static_assert(meta::value_of("FF0_F1|FF0_F2") == FF0_F5); + static_assert(meta::value_of("FF0_F6") == FF0_F6); + static_assert(meta::value_of("FF0_F3|FF0_F4") == FF0_F6); + static_assert(meta::value_of("FF0_F3|FF0_F1337", static_cast(42)) == 42); + static_assert(meta::value_of("FF0_F3|FF0_F1337") == FF0_F3); + static_assert(meta::value_of("FF0_F7", static_cast(42)) == 42); + + static_assert(meta::value_of("FF1_F1") == FF1_F1); + static_assert(meta::value_of("FF1_F2") == FF1_F2); + static_assert(meta::value_of("FF1_F3") == FF1_F3); + static_assert(meta::value_of("FF1_F4") == FF1_F4); + static_assert(meta::value_of("FF1_F5") == FF1_F5); + static_assert(meta::value_of("FF1_F1|FF1_F2") == FF1_F5); + static_assert(meta::value_of("FF1_F6") == FF1_F6); + static_assert(meta::value_of("FF1_F3|FF1_F4") == FF1_F6); + static_assert(meta::value_of("FF1_F3|FF1_F1337", static_cast(42)) == 42); + static_assert(meta::value_of("FF1_F3|FF1_F1337") == FF1_F3); + static_assert(meta::value_of("FF1_F7", static_cast(42)) == 42); + + static_assert(meta::value_of("ScopedFlag0::F0", "+") == ScopedFlag0::F0); + static_assert(meta::value_of("ScopedFlag0::F1", "+") == ScopedFlag0::F1); + static_assert(meta::value_of("ScopedFlag0::F2", "+") == ScopedFlag0::F2); + static_assert(meta::value_of("ScopedFlag0::F3", "+") == ScopedFlag0::F3); + static_assert(meta::value_of("ScopedFlag0::F4", "+") == ScopedFlag0::F4); + static_assert(meta::value_of("ScopedFlag0::F5", "+") == ScopedFlag0::F5); + static_assert(meta::value_of("ScopedFlag0::F1+ScopedFlag0::F2", "+") == ScopedFlag0::F5); + static_assert(meta::value_of("ScopedFlag0::F6", "+") == ScopedFlag0::F6); + static_assert(meta::value_of("ScopedFlag0::F3+ScopedFlag0::F4", "+") == ScopedFlag0::F6); + static_assert( + meta::value_of("ScopedFlag0::F3+ScopedFlag0::F1337", "+", static_cast(42)) == + static_cast(42) + ); + static_assert(meta::value_of("ScopedFlag0::F3+ScopedFlag0::F1337", "+") == ScopedFlag0::F3); + static_assert( + meta::value_of("ScopedFlag0::F7", "+", static_cast(42)) == + static_cast(42) + ); + + static_assert(meta::value_of("ScopedFlag1::F1", "-") == ScopedFlag1::F1); + static_assert(meta::value_of("ScopedFlag1::F2", "-") == ScopedFlag1::F2); + static_assert(meta::value_of("ScopedFlag1::F3", "-") == ScopedFlag1::F3); + static_assert(meta::value_of("ScopedFlag1::F4", "-") == ScopedFlag1::F4); + static_assert(meta::value_of("ScopedFlag1::F5", "-") == ScopedFlag1::F5); + static_assert(meta::value_of("ScopedFlag1::F1-ScopedFlag1::F2", "-") == ScopedFlag1::F5); + static_assert(meta::value_of("ScopedFlag1::F6", "-") == ScopedFlag1::F6); + static_assert(meta::value_of("ScopedFlag1::F3-ScopedFlag1::F4", "-") == ScopedFlag1::F6); + static_assert( + meta::value_of("ScopedFlag1::F3-ScopedFlag1::F1337", "-", static_cast(42)) == + static_cast(42) + ); + static_assert(meta::value_of("ScopedFlag1::F3-ScopedFlag1::F1337", "-") == ScopedFlag1::F3); + static_assert( + meta::value_of("ScopedFlag1::F7", "-", static_cast(42)) == + static_cast(42) + ); } diff --git a/unit_test/src/meta/member.cpp b/unit_test/src/meta/member.cpp new file mode 100644 index 00000000..f7b30387 --- /dev/null +++ b/unit_test/src/meta/member.cpp @@ -0,0 +1,451 @@ +// meta::member +#include + +#if not(defined(GAL_PROMETHEUS_COMPILER_MSVC) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL)) +#include +#endif + +#include + +using namespace gal::prometheus; + +namespace +{ + struct const_left_reference + { + [[maybe_unused]] int id; + + [[nodiscard]] constexpr auto operator==(const const_left_reference&) const & noexcept -> bool { return true; } + [[nodiscard]] constexpr auto operator==(const_left_reference&&) const & noexcept -> bool { return false; } + // ReSharper disable CppMemberFunctionMayBeConst + [[nodiscard]] constexpr auto operator==(const const_left_reference&) && noexcept -> bool { return false; } + [[nodiscard]] constexpr auto operator==(const_left_reference&&) && noexcept -> bool { return false; } + // ReSharper restore CppMemberFunctionMayBeConst + }; + + struct right_reference + { + [[maybe_unused]] int id; + + [[nodiscard]] constexpr auto operator==(const right_reference&) const & noexcept -> bool { return false; } + [[nodiscard]] constexpr auto operator==(right_reference&&) const & noexcept -> bool { return false; } + // ReSharper disable CppMemberFunctionMayBeConst + [[nodiscard]] constexpr auto operator==(const right_reference&) && noexcept -> bool { return false; } + [[nodiscard]] constexpr auto operator==(right_reference&&) && noexcept -> bool { return true; } + // ReSharper restore CppMemberFunctionMayBeConst + }; + + class MyTupleLike + { + public: + template + using type = std::conditional_t< + Index == 0, + const_left_reference, + std::conditional_t< + Index == 1, + right_reference, + std::string + > + >; + + private: + const_left_reference a_; + right_reference b_; + std::string c_; + + friend struct meta::extern_accessor; + + constexpr MyTupleLike() noexcept + : a_{}, + b_{} {} + + public: + constexpr explicit MyTupleLike(const const_left_reference a, const right_reference b, std::string c) noexcept + : a_{a}, + b_{b}, + c_{std::move(c)} {} + + template + [[nodiscard]] constexpr auto get() & noexcept -> auto& + { + if constexpr (Index == 0) + { + return a_; + } + else if constexpr (Index == 1) + { + return b_; + } + else if constexpr (Index == 2) + { + return c_; + } + } + + template + [[nodiscard]] constexpr auto get() const & noexcept -> const auto& + { + if constexpr (Index == 0) + { + return a_; + } + else if constexpr (Index == 1) + { + return b_; + } + else if constexpr (Index == 2) + { + return c_; + } + } + + template + [[nodiscard]] constexpr auto get() && noexcept -> auto&& + { + if constexpr (Index == 0) + { + return std::move(a_); + } + else if constexpr (Index == 1) + { + return std::move(b_); + } + else if constexpr (Index == 2) + { + return std::move(c_); + } + } + }; + + struct my_aggregate + { + [[maybe_unused]] const_left_reference a; + [[maybe_unused]] right_reference b; + [[maybe_unused]] std::string c; + }; + + static_assert(std::is_aggregate_v == false); + static_assert(std::is_aggregate_v == true); +} + +namespace std +{ + template + struct tuple_element + { + using type = MyTupleLike::type; + }; + + template<> + struct tuple_size : std::integral_constant {}; +} + +namespace +{ + static_assert(meta::member_size() == 3); + static_assert(meta::member_size() == 3); + + template + [[nodiscard]] constexpr auto test_clr() noexcept -> bool + { + constexpr const_left_reference a{.id = __LINE__}; + constexpr right_reference b{.id = __LINE__}; + const std::string c{"hello world"}; + + const T object{a, b, c}; + + if constexpr (Index == 0) + { + return meta::member_of_index<0>(object) == a; + } + else if constexpr (Index == 1) + { + return meta::member_of_index<1>(object) == b; + } + else if constexpr (Index == 2) + { + return meta::member_of_index<2>(object) == c; + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + template + [[nodiscard]] constexpr auto test_rr() noexcept -> bool + { + constexpr const_left_reference a{.id = __LINE__}; + constexpr right_reference b{.id = __LINE__}; + const std::string c{"hello world"}; + + T object{a, b, c}; + + if constexpr (Index == 0) + { + return meta::member_of_index<0>(std::move(object)) == const_left_reference{a}; + } + else if constexpr (Index == 1) + { + return meta::member_of_index<1>(std::move(object)) == right_reference{b}; + } + else if constexpr (Index == 2) + { + return meta::member_of_index<2>(std::move(object)) == std::string{c}; + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + } + + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) + + static_assert(test_clr() == true); + static_assert(test_clr() == false); + static_assert(test_clr() == true); + static_assert(test_rr() == false); + static_assert(test_rr() == true); + static_assert(test_rr() == true); + + static_assert(test_clr() == true); + static_assert(test_clr() == false); + static_assert(test_clr() == true); + static_assert(test_rr() == false); + static_assert(test_rr() == true); + static_assert(test_rr() == true); + + #else + + // error: non-constant condition for static assertion + // static_assert(test_clr() == true); + // ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~ + // error: accessing ‘std::__cxx11::basic_string::::_M_allocated_capacity’ member instead of initialized ‘std::__cxx11::basic_string::::_M_local_buf’ member in constant expression + // error: non-constant condition for static assertion + + [[maybe_unused]] GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"meta.member.member_of_index"> member_of_index = [] + { + using namespace unit_test; + + "MyTupleLike"_test = [] + { + expect(test_clr() == "equal"_b) << fatal; + expect(test_clr() != "not equal"_b) << fatal; + expect(test_clr() == "equal"_b) << fatal; + expect(test_rr() != "not equal"_b) << fatal; + expect(test_rr() == "equal"_b) << fatal; + expect(test_rr() == "equal"_b) << fatal; + }; + + "my_aggregate"_test = [] + { + expect(test_clr() == "equal"_b) << fatal; + expect(test_clr() != "not equal"_b) << fatal; + expect(test_clr() == "equal"_b) << fatal; + expect(test_rr() != "not equal"_b) << fatal; + expect(test_rr() == "equal"_b) << fatal; + expect(test_rr() == "equal"_b) << fatal; + }; + }; + + #endif + + static_assert(meta::name_of_member<0, MyTupleLike>() == "a_"); + static_assert(meta::name_of_member<1, MyTupleLike>() == "b_"); + static_assert(meta::name_of_member<2, MyTupleLike>() == "c_"); + + static_assert(meta::name_of_member<0, my_aggregate>() == "a"); + static_assert(meta::name_of_member<1, my_aggregate>() == "b"); + static_assert(meta::name_of_member<2, my_aggregate>() == "c"); + + static_assert(meta::member_index<"a_", MyTupleLike>() == 0); + static_assert(meta::member_index<"b_", MyTupleLike>() == 1); + static_assert(meta::member_index<"c_", MyTupleLike>() == 2); + + static_assert(meta::member_index<"a", my_aggregate>() == 0); + static_assert(meta::member_index<"b", my_aggregate>() == 1); + static_assert(meta::member_index<"c", my_aggregate>() == 2); + + static_assert(meta::member_index("a_") == 0); + static_assert(meta::member_index("b_") == 1); + static_assert(meta::member_index("c_") == 2); + + static_assert(meta::member_index("a") == 0); + static_assert(meta::member_index("b") == 1); + static_assert(meta::member_index("c") == 2); + + template + [[nodiscard]] constexpr auto test_walk() noexcept -> bool + { + constexpr const_left_reference a{.id = __LINE__}; + constexpr right_reference b{.id = __LINE__}; + const std::string c{"hello world"}; + + const T object1{a, b, c}; + const T object2{a, b, c}; + + T object{object1}; + meta::member_walk( + [](auto& o, const auto& o1, const auto& o2) noexcept -> void + { + if constexpr (Index == 0) + { + // const_left_reference + o.id += (o1.id + o2.id); + } + else if constexpr (Index == 1) + { + // right_reference + o.id -= (o1.id + o2.id); + } + else if constexpr (Index == 2) + { + // std::string + o.append("-"); + // o.append_range(o1); + o.insert(o.end(), o1.begin(), o1.end()); + o.append("-"); + // o.append_range(o2); + o.insert(o.end(), o2.begin(), o2.end()); + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + }, + object, + object1, + object2 + ); + + const auto& [o_a, o_b, o_c] = object; + std::string s{c}; + s.append("-"); + // s.append_range(c); + s.insert(s.end(), c.begin(), c.end()); + s.append("-"); + // s.append_range(c); + s.insert(s.end(), c.begin(), c.end()); + + return // + o_a.id == a.id + (a.id + a.id) and + o_b.id == b.id - (b.id + b.id) and + o_c == s; + } + + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) + + static_assert(test_walk() == true); + static_assert(test_walk() == true); + + #else + + // error: non-constant condition for static assertion + // static_assert(test_walk() == true); + // ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~ + // error: accessing ‘std::__cxx11::basic_string::::_M_allocated_capacity’ member instead of initialized ‘std::__cxx11::basic_string::::_M_local_buf’ member in constant expression + // error: non-constant condition for static assertion + + [[maybe_unused]] GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"meta.member.member_walk"> member_walk = [] + { + using namespace unit_test; + + "MyTupleLike"_test = [] + { + expect(test_walk() == "equal"_b) << fatal; + }; + + "my_aggregate"_test = [] + { + expect(test_walk() == "equal"_b) << fatal; + }; + }; + + #endif + + template + [[nodiscard]] constexpr auto test_walk_until() noexcept -> bool + { + constexpr const_left_reference a{.id = __LINE__}; + constexpr right_reference b{.id = __LINE__}; + const std::string c{"hello world"}; + + const T object1{a, b, c}; + const T object2{a, b, c}; + + T object{object1}; + meta::member_walk_until( + [](auto& o, const auto& o1, const auto& o2) noexcept -> bool + { + if constexpr (Index == 0) + { + // const_left_reference + o.id += (o1.id + o2.id); + return true; + } + else if constexpr (Index == 1) + { + // right_reference + o.id -= (o1.id + o2.id); + return false; + } + else if constexpr (Index == 2) + { + // std::string + o.append("-"); + // o.append_range(o1); + o.insert(o.end(), o1.begin(), o1.end()); + o.append("-"); + // o.append_range(o2); + o.insert(o.end(), o2.begin(), o2.end()); + return true; + } + else + { + GAL_PROMETHEUS_SEMANTIC_STATIC_UNREACHABLE(); + } + }, + object, + object1, + object2 + ); + + const auto& [o_a, o_b, o_c] = object; + + return // + o_a.id == a.id + (a.id + a.id) and + o_b.id == b.id - (b.id + b.id) and + o_c == c; + } + + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) or defined(GAL_PROMETHEUS_COMPILER_CLANG_CL) + + static_assert(test_walk_until() == true); + static_assert(test_walk_until() == true); + + #else + + // error: non-constant condition for static assertion + // static_assert(test_walk_until() == true); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~ + // error: accessing ‘std::__cxx11::basic_string::::_M_allocated_capacity’ member instead of initialized ‘std::__cxx11::basic_string::::_M_local_buf’ member in constant expression + // error: non-constant condition for static assertion + + [[maybe_unused]] GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"meta.member.member_walk_until"> member_walk_until = [] + { + using namespace unit_test; + + "MyTupleLike"_test = [] + { + expect(test_walk_until() == "equal"_b) << fatal; + }; + + "my_aggregate"_test = [] + { + expect(test_walk_until() == "equal"_b) << fatal; + }; + }; + + #endif +} diff --git a/unit_test/src/string/string_pool.cpp b/unit_test/src/string/string_pool.cpp new file mode 100644 index 00000000..512ed538 --- /dev/null +++ b/unit_test/src/string/string_pool.cpp @@ -0,0 +1,234 @@ +#include +#include + +#include +// string::string_pool +#include +// numeric::random +#include + +using namespace gal::prometheus; + +namespace +{ + #if defined(GAL_PROMETHEUS_COMPILER_MSVC) + // see https://developercommunity.visualstudio.com/t/Nested-template-lambda-accidentally-ig/10802570 + #define WORKAROUND_MSVC_C3688 using namespace unit_test; + #else + #define WORKAROUND_MSVC_C3688 + #endif + + template + [[nodiscard]] constexpr auto make_random_string(const std::size_t length) noexcept -> std::basic_string + { + numeric::Random random{}; + + std::basic_string result{}; + result.reserve(length); + + std::ranges::generate_n( + std::back_inserter(result), + length, + [&random]() noexcept -> CharType + { + return static_cast(random.get()); + } + ); + return result; + } + + GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"string.string_pool"> _ = [] + { + using namespace unit_test; + using namespace string; + + const auto test_pool = []() noexcept -> void + { + WORKAROUND_MSVC_C3688 + + "copy"_test = [] + { + using pool_type = StringPool; + + constexpr auto length = 1000; + expect(length + IsNullTerminate < value(pool_type::default_block_initial_size)); + + pool_type p1{}; + std::ignore = p1.add(make_random_string(p1.block_initial_size())); + std::ignore = p1.add(make_random_string(length)); + expect(p1.size() == 2_ull) << fatal; + + pool_type p2{}; + std::ignore = p2.add(make_random_string(length)); + std::ignore = p2.add(make_random_string(p2.block_initial_size())); + expect(p2.size() == 2_ull) << fatal; + + pool_type p3{}; + std::ignore = p3.add(make_random_string(length)); + std::ignore = p3.add(make_random_string(length)); + expect(p3.size() == 1_ull) << fatal; + + pool_type p{p1, p2, p3}; + expect(p1.size() == 2_ull) << fatal; + expect(p2.size() == 2_ull) << fatal; + expect(p3.size() == 1_ull) << fatal; + expect(p.size() == 5_ull) << fatal; + + p.join(p1, p2, p3); + expect(p1.size() == 2_ull) << fatal; + expect(p2.size() == 2_ull) << fatal; + expect(p3.size() == 1_ull) << fatal; + expect(p.size() == 10_ull) << fatal; + }; + + "move"_test = [] + { + using pool_type = StringPool; + + constexpr auto length = 1000; + expect(length + IsNullTerminate < value(pool_type::default_block_initial_size)); + + pool_type p1{}; + std::ignore = p1.add(make_random_string(p1.block_initial_size())); + std::ignore = p1.add(make_random_string(length)); + expect(p1.size() == 2_ull) << fatal; + pool_type p1_copy{p1}; + + pool_type p2{}; + std::ignore = p2.add(make_random_string(length)); + std::ignore = p2.add(make_random_string(p2.block_initial_size())); + expect(p2.size() == 2_ull) << fatal; + pool_type p2_copy{p2}; + + pool_type p3{}; + std::ignore = p3.add(make_random_string(length)); + std::ignore = p3.add(make_random_string(length)); + expect((length + IsNullTerminate) * 2 < value(pool_type::default_block_initial_size)) << fatal; + expect(p3.size() == 1_ull) << fatal; + pool_type p3_copy{p3}; + + pool_type p{std::move(p1), std::move(p2), std::move(p3)}; + expect(p1.size() == 0_ull) << fatal; // NOLINT(bugprone-use-after-move) + expect(p2.size() == 0_ull) << fatal; // NOLINT(bugprone-use-after-move) + expect(p3.size() == 0_ull) << fatal; // NOLINT(bugprone-use-after-move) + expect(p.size() == 5_ull) << fatal; + + p.join(std::move(p1_copy), std::move(p2_copy), std::move(p3_copy)); + expect(p1_copy.size() == 0_ull) << fatal; // NOLINT(bugprone-use-after-move) + expect(p2_copy.size() == 0_ull) << fatal; // NOLINT(bugprone-use-after-move) + expect(p3_copy.size() == 0_ull) << fatal; // NOLINT(bugprone-use-after-move) + expect(p.size() == 10_ull) << fatal; + }; + + "copy_and_move"_test = [] + { + using pool_type = StringPool; + + constexpr auto length = 1000; + expect(length + IsNullTerminate < value(pool_type::default_block_initial_size)); + + pool_type p1{}; + std::ignore = p1.add(make_random_string(p1.block_initial_size())); + std::ignore = p1.add(make_random_string(length)); + expect(p1.size() == 2_ull) << fatal; + + pool_type p2{}; + std::ignore = p2.add(make_random_string(length)); + std::ignore = p2.add(make_random_string(p2.block_initial_size())); + expect(p2.size() == 2_ull) << fatal; + + pool_type p3{}; + std::ignore = p3.add(make_random_string(length)); + std::ignore = p3.add(make_random_string(length)); + expect((length + IsNullTerminate) * 2 < value(pool_type::default_block_initial_size)) << fatal; + expect(p3.size() == 1_ull) << fatal; + + pool_type p{std::move(p1), p2, std::move(p3)}; + expect(p1.size() == 0_ull) << fatal; // NOLINT(bugprone-use-after-move) + expect(p2.size() == 2_ull) << fatal; + expect(p3.size() == 0_ull) << fatal; // NOLINT(bugprone-use-after-move) + expect(p.size() == 5_ull) << fatal; + }; + }; + + const auto test_block_size = []() noexcept -> void + { + WORKAROUND_MSVC_C3688 + + using pool_type = StringPool; + + expect(200 + IsNullTerminate < value(pool_type::default_block_initial_size)); + + pool_type p{100}; + + std::ignore = p.add(make_random_string(200)); + std::ignore = p.add(make_random_string(50)); + // (200 + IsNullTerminate) / (200 + IsNullTerminate) + // (50 + IsNullTerminate) / (100) + expect(p.size() == 2_ull) << fatal; + + p.reset_block_initial_size(200); + std::ignore = p.add(make_random_string(200)); + std::ignore = p.add(make_random_string(50)); + if constexpr (IsNullTerminate) + { + // (200 + IsNullTerminate) / (200 + IsNullTerminate) + // (200 + IsNullTerminate) / (200 + IsNullTerminate) + // (50 + IsNullTerminate) / (100) + // (50 + IsNullTerminate) / (200) + expect(p.size() == 4_ull) << fatal; + + std::ignore = p.add(make_random_string(200 - 51 - IsNullTerminate)); + // (200 + IsNullTerminate) / (200 + IsNullTerminate) + // (200 + IsNullTerminate) / (200 + IsNullTerminate) + // (50 + IsNullTerminate) + (200 - 51 - IsNullTerminate) / 200 + // (50 + IsNullTerminate) / 100 + expect(p.size() == 4_ull) << fatal; + + std::ignore = p.add(make_random_string(100 - 51 - IsNullTerminate)); + // (200 + IsNullTerminate) / (200 + IsNullTerminate) + // (200 + IsNullTerminate) / (200 + IsNullTerminate) + // (50 + IsNullTerminate) + (200 - 51 - IsNullTerminate) / 200 + // (50 + IsNullTerminate) + (100 - 51 - IsNullTerminate) / 100 + expect(p.size() == 4_ull) << fatal; + } + else + { + // (200) / (200) + // (200) / (200) + // (50) + (50) / (100) + expect(p.size() == 3_ull) << fatal; + } + }; + + "pool"_test = [test_pool] + { + test_pool.operator()(); + test_pool.operator()(); + test_pool.operator()(); + test_pool.operator()(); + test_pool.operator()(); + + test_pool.operator()(); + test_pool.operator()(); + test_pool.operator()(); + test_pool.operator()(); + test_pool.operator()(); + }; + + "block_size"_test = [test_block_size] + { + test_block_size.operator()(); + test_block_size.operator()(); + test_block_size.operator()(); + test_block_size.operator()(); + test_block_size.operator()(); + + test_block_size.operator()(); + test_block_size.operator()(); + test_block_size.operator()(); + test_block_size.operator()(); + test_block_size.operator()(); + }; + }; +} diff --git a/unit_test/src/utility/function_ref.cpp b/unit_test/src/utility/function_ref.cpp deleted file mode 100644 index 49b3b4b1..00000000 --- a/unit_test/src/utility/function_ref.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include - -import std; -import gal.prometheus.test; -import gal.prometheus.utility; - -namespace -{ - using namespace gal::prometheus; - using namespace utility; - - GAL_PROMETHEUS_NO_DESTROY test::suite<"utility.function_ref"> _ = [] - { - using namespace test; - - ignore_pass / "functor"_test = [] - { - struct functor - { - constexpr auto operator()(const int a, const int b) const noexcept -> int { return a + b; } - - constexpr auto operator()(int& a) const noexcept -> void { a = 42; } - }; - - functor f{}; - - FunctionRef a{f}; - expect(a(42, 1337) == as_i{42 + 1337}) << fatal; - - FunctionRef b{f}; - expect(b(42, 1337) == as_i{42 + 1337}) << fatal; - - int v = 1337; - expect(v == 1337_i) << fatal; - - FunctionRef c{f}; - c(v); - expect(v == 42_i) << fatal; - - v = 1337; - FunctionRef d{f}; - // note: call functor::operator()(const int a, const int b) - d(v, 123); - expect(v == 1337_i) << fatal; - }; - - ignore_pass / "function pointer"_test = [] - { - const auto f = +[](const int a, const int b) noexcept -> int { return a + b; }; - - FunctionRef a{f}; - expect(a(42, 1337) == as_i{42 + 1337}) << fatal; - - FunctionRef b{f}; - expect(b(42, 1337) == as_i{42 + 1337}) << fatal; - - // compatible - FunctionRef c{f}; - c(42, 1337); - }; - - ignore_pass / "lambda"_test = [] - { - { - const auto f = [](const int a, const int b) noexcept -> int { return a + b; }; - - FunctionRef a{f}; - expect(a(42, 1337) == as_i{42 + 1337}) << fatal; - - FunctionRef b{f}; - expect(b(42, 1337) == as_i{42 + 1337}) << fatal; - - // compatible - FunctionRef c{f}; - c(42, 1337); - } - { - int i = 42; - const auto f = [&i](const int a, const int b) noexcept -> int { return i + a + b; }; - - FunctionRef a{f}; - expect(a(42, 1337) == as_i{i + 42 + 1337}) << fatal; - - FunctionRef b{f}; - expect(b(42, 1337) == as_i{i + 42 + 1337}) << fatal; - - // compatible - FunctionRef c{f}; - c(42, 1337); - } - }; - - ignore_pass / "member function"_test = [] - { - class Foo - { - int _{0}; - - public: - [[nodiscard]] constexpr auto bar(const int a, const int b) noexcept -> int// NOLINT - { - _ = a + b; - - return _; - } - }; - - Foo foo{}; - - FunctionRef a{[](Foo& f, const int v1, const int v2) noexcept -> int { return f.bar(v1, v2); }}; - expect(a(foo, 42, 1337) == as_i{42 + 1337}) << fatal; - - auto function_pointer = std::mem_fn(&Foo::bar); - FunctionRef b{function_pointer}; - expect(b(foo, 42, 1337) == as_i{42 + 1337}) << fatal; - }; - }; -} diff --git a/unit_test/src/wildcard/wildcard.cpp b/unit_test/src/wildcard/wildcard.cpp index 20f73545..0d4e13c9 100644 --- a/unit_test/src/wildcard/wildcard.cpp +++ b/unit_test/src/wildcard/wildcard.cpp @@ -1,12 +1,13 @@ #include #if GAL_PROMETHEUS_USE_MODULE -import std; import gal.prometheus; #else #include #endif +#include + using namespace gal::prometheus; namespace @@ -55,7 +56,6 @@ namespace GAL_PROMETHEUS_COMPILER_NO_DESTROY unit_test::suite<"wildcard.wildcard"> _ = [] { using namespace unit_test; - using namespace literals; "basic_test"_test = [] {